GP-2446: Add margin provider to decompiler. Port line numbers to it.

This commit is contained in:
Dan 2022-10-04 14:42:41 -04:00
parent 7e24c986ad
commit e72aa6e039
16 changed files with 520 additions and 259 deletions

View file

@ -0,0 +1,37 @@
/* ###
* 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.decompiler;
import ghidra.app.decompiler.component.margin.DecompilerMarginProvider;
/**
* A service that allows clients to add custom margins in the Decompiler UI.
*/
public interface DecompilerMarginService {
/**
* Add a margin to the Decompiler's primary window
*
* @param provider the margin provider
*/
void addMarginProvider(DecompilerMarginProvider provider);
/**
* Remove a margin from the Decompiler's primary window
*
* @param provider the margin provider
*/
void removeMarginProvider(DecompilerMarginProvider provider);
}

View file

@ -159,7 +159,7 @@ public class CDisplayPanel extends JPanel implements DecompilerCallbackHandler {
} }
@Override @Override
public void doWheNotBusy(Callback c) { public void doWhenNotBusy(Callback c) {
// stub // stub
} }

View file

@ -21,8 +21,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.*; import java.util.regex.*;
import javax.swing.JComponent;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import docking.widgets.SearchLocation; import docking.widgets.SearchLocation;
@ -47,10 +45,7 @@ import ghidra.util.Msg;
*/ */
public class ClangLayoutController implements LayoutModel, LayoutModelListener { public class ClangLayoutController implements LayoutModel, LayoutModelListener {
private final ClangFieldElement EMPTY_LINE_NUMBER_SPACER;
private int maxWidth; private int maxWidth;
private int lineNumberFieldWidth;
private int indentWidth; private int indentWidth;
private DecompileOptions options; private DecompileOptions options;
private DecompilerPanel decompilerPanel; private DecompilerPanel decompilerPanel;
@ -59,26 +54,19 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
private FontMetrics metrics; private FontMetrics metrics;
private HighlightFactory hlFactory; private HighlightFactory hlFactory;
private ArrayList<LayoutModelListener> listeners; private ArrayList<LayoutModelListener> listeners;
private Color[] syntax_color; // Foreground colors. private Color[] syntaxColor; // Foreground colors.
private BigInteger numIndexes = BigInteger.ZERO; private BigInteger numIndexes = BigInteger.ZERO;
private ArrayList<ClangLine> lines = new ArrayList<>(); private ArrayList<ClangLine> lines = new ArrayList<>();
private boolean showLineNumbers = true; private boolean showLineNumbers = true;
private ClangFieldElement createEmptyLineNumberSpacer() {
ClangToken lineNumberToken = ClangToken.buildSpacer(null, 0, "");
AttributedString as = new AttributedString("", Color.WHITE, metrics);
return new ClangFieldElement(lineNumberToken, as, 0);
}
public ClangLayoutController(DecompileOptions opt, DecompilerPanel decompilerPanel, public ClangLayoutController(DecompileOptions opt, DecompilerPanel decompilerPanel,
FontMetrics met, HighlightFactory hlFactory) { FontMetrics met, HighlightFactory hlFactory) {
options = opt; options = opt;
this.decompilerPanel = decompilerPanel; this.decompilerPanel = decompilerPanel;
syntax_color = new Color[ClangToken.MAX_COLOR]; syntaxColor = new Color[ClangToken.MAX_COLOR];
metrics = met; metrics = met;
this.hlFactory = hlFactory; this.hlFactory = hlFactory;
EMPTY_LINE_NUMBER_SPACER = createEmptyLineNumberSpacer();
listeners = new ArrayList<>(); listeners = new ArrayList<>();
buildLayouts(null, null, null, false); buildLayouts(null, null, null, false);
} }
@ -94,7 +82,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
@Override @Override
public Dimension getPreferredViewSize() { public Dimension getPreferredViewSize() {
return new Dimension(maxWidth + lineNumberFieldWidth, 500); return new Dimension(maxWidth, 500);
} }
@Override @Override
@ -179,15 +167,11 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
boolean paintLineNumbers) { boolean paintLineNumbers) {
List<ClangToken> tokens = line.getAllTokens(); List<ClangToken> tokens = line.getAllTokens();
ClangFieldElement lineNumberFieldElement =
createLineNumberFieldElement(line, lineCount, paintLineNumbers);
FieldElement[] elements = createFieldElementsForLine(tokens); FieldElement[] elements = createFieldElementsForLine(tokens);
int indent = line.getIndent() * indentWidth; int indent = line.getIndent() * indentWidth;
int lineNumberWidth = lineNumberFieldElement.getStringWidth(); int updatedMaxWidth = maxWidth;
int updatedMaxWidth = maxWidth + lineNumberWidth; return new ClangTextField(tokens, elements, indent, line.getLineNumber(), updatedMaxWidth,
return new ClangTextField(tokens, elements, lineNumberFieldElement, indent, updatedMaxWidth,
hlFactory); hlFactory);
} }
@ -197,7 +181,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
int columnPosition = 0; int columnPosition = 0;
for (int i = 0; i < tokens.size(); ++i) { for (int i = 0; i < tokens.size(); ++i) {
ClangToken token = tokens.get(i); ClangToken token = tokens.get(i);
Color color = syntax_color[token.getSyntaxType()]; Color color = syntaxColor[token.getSyntaxType()];
if (token instanceof ClangCommentToken) { if (token instanceof ClangCommentToken) {
AttributedString prototype = new AttributedString("prototype", color, metrics); AttributedString prototype = new AttributedString("prototype", color, metrics);
Program program = decompilerPanel.getProgram(); Program program = decompilerPanel.getProgram();
@ -214,32 +198,22 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
return elements; return elements;
} }
private ClangFieldElement createLineNumberFieldElement(ClangLine line, int lineCount,
boolean paintLineNumbers) {
if (paintLineNumbers) {
return new LineNumberFieldElement(line.getLineNumber(), lineCount, metrics);
}
return EMPTY_LINE_NUMBER_SPACER;
}
/** /**
* Update to the current Decompiler display options * Update to the current Decompiler display options
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
// ignoring the deprecated call for toolkit // ignoring the deprecated call for toolkit
private void updateOptions() { private void updateOptions() {
syntax_color[ClangToken.KEYWORD_COLOR] = options.getKeywordColor(); syntaxColor[ClangToken.KEYWORD_COLOR] = options.getKeywordColor();
syntax_color[ClangToken.TYPE_COLOR] = options.getTypeColor(); syntaxColor[ClangToken.TYPE_COLOR] = options.getTypeColor();
syntax_color[ClangToken.FUNCTION_COLOR] = options.getFunctionColor(); syntaxColor[ClangToken.FUNCTION_COLOR] = options.getFunctionColor();
syntax_color[ClangToken.COMMENT_COLOR] = options.getCommentColor(); syntaxColor[ClangToken.COMMENT_COLOR] = options.getCommentColor();
syntax_color[ClangToken.VARIABLE_COLOR] = options.getVariableColor(); syntaxColor[ClangToken.VARIABLE_COLOR] = options.getVariableColor();
syntax_color[ClangToken.CONST_COLOR] = options.getConstantColor(); syntaxColor[ClangToken.CONST_COLOR] = options.getConstantColor();
syntax_color[ClangToken.PARAMETER_COLOR] = options.getParameterColor(); syntaxColor[ClangToken.PARAMETER_COLOR] = options.getParameterColor();
syntax_color[ClangToken.GLOBAL_COLOR] = options.getGlobalColor(); syntaxColor[ClangToken.GLOBAL_COLOR] = options.getGlobalColor();
syntax_color[ClangToken.DEFAULT_COLOR] = options.getDefaultColor(); syntaxColor[ClangToken.DEFAULT_COLOR] = options.getDefaultColor();
syntax_color[ClangToken.ERROR_COLOR] = options.getErrorColor(); syntaxColor[ClangToken.ERROR_COLOR] = options.getErrorColor();
// setting the metrics here will indirectly trigger the new font to be used deeper in // setting the metrics here will indirectly trigger the new font to be used deeper in
// the bowels of the FieldPanel (you can get the font from the metrics) // the bowels of the FieldPanel (you can get the font from the metrics)
@ -247,7 +221,6 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
metrics = Toolkit.getDefaultToolkit().getFontMetrics(font); metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
indentWidth = metrics.stringWidth(PrettyPrinter.INDENT_STRING); indentWidth = metrics.stringWidth(PrettyPrinter.INDENT_STRING);
maxWidth = indentWidth * options.getMaxWidth(); maxWidth = indentWidth * options.getMaxWidth();
lineNumberFieldWidth = 0;
showLineNumbers = options.isDisplayLineNumbers(); showLineNumbers = options.isDisplayLineNumbers();
} }
@ -264,11 +237,6 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
fieldList = new Field[lineCount]; // One field for each "C" line fieldList = new Field[lineCount]; // One field for each "C" line
numIndexes = BigInteger.valueOf(lineCount); numIndexes = BigInteger.valueOf(lineCount);
lineNumberFieldWidth = 0;
if (showLineNumbers && !isError) {
lineNumberFieldWidth = LineNumberFieldElement.getFieldWidth(metrics, lineCount);
}
for (int i = 0; i < lineCount; ++i) { for (int i = 0; i < lineCount; ++i) {
ClangLine oneLine = lines.get(i); ClangLine oneLine = lines.get(i);
fieldList[i] = createTextFieldForLine(oneLine, lineCount, showLineNumbers); fieldList[i] = createTextFieldForLine(oneLine, lineCount, showLineNumbers);
@ -600,81 +568,6 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private static class LineNumberFieldElement extends ClangFieldElement {
private static final Color FOREGROUND_COLOR = new Color(125, 125, 125);
private int uniformWidth;
private LineNumberFieldElement(int lineNumber, int lineCount, FontMetrics fontMetrics) {
super(ClangToken.buildSpacer(null, 0, ""), createAttributedLineNumberString(lineNumber,
lineCount, FOREGROUND_COLOR, fontMetrics), 0);
uniformWidth = calculateUniformStringWidth(fontMetrics);
}
private static String createLineNumberString(int lineNumber, int lineCount) {
String lineCountString = Integer.toString(lineCount);
int maxNumberOfDigits = lineCountString.length();
String lineNumberString = Integer.toString(lineNumber);
int lineNumberLength = lineNumberString.length();
int padLength = maxNumberOfDigits - lineNumberLength;
StringBuffer buffy = new StringBuffer();
for (int i = 0; i < padLength; i++) {
buffy.append(' ');
}
buffy.append(lineNumberString).append(' '); // space for separation
return buffy.toString();
}
private static AttributedString createAttributedLineNumberString(int lineNumber,
int lineCount, Color foregroundColor, FontMetrics fontMetrics) {
return new AttributedString(createLineNumberString(lineNumber, lineCount),
foregroundColor, fontMetrics);
}
static int getFieldWidth(FontMetrics fontMetrics, int lineCnt) {
int largestCharacterWidth = getLargestCharacterWidth(fontMetrics);
int numberOfCharacters = createLineNumberString(0, lineCnt).length();
return numberOfCharacters * largestCharacterWidth;
}
private int calculateUniformStringWidth(FontMetrics fontMetrics) {
int largestCharacterWidth = getLargestCharacterWidth(fontMetrics);
int numberOfCharacters = getText().length();
return numberOfCharacters * largestCharacterWidth;
}
private static int getLargestCharacterWidth(FontMetrics fontMetrics) {
// use the biggest number char (since that's what we paint in this object)
// for determining the a space to use as a guide
return fontMetrics.stringWidth("9");
}
@Override
public void paint(JComponent c, Graphics g, int x, int y) {
// paint our text
super.paint(c, g, 0, 0);
// paint a vertical rule
Color color = getColor(0);
g.setColor(color);
FontMetrics fontMetrics = g.getFontMetrics();
int topX = fontMetrics.getMaxAscent() + 1; // fudge for font painting differences
int maxDescent = fontMetrics.getMaxDescent();
int baselineX = maxDescent + 1; // fudge for font painting differences
g.drawLine(uniformWidth, -topX, uniformWidth, baselineX);
}
@Override
// overridden so that our width reflects our custom width
public int getStringWidth() {
return uniformWidth + 3; // fudge for keeping the c code off the line number bar
}
}
private class FieldNumberColumnPair { private class FieldNumberColumnPair {
private final int fieldNumber; private final int fieldNumber;
private final int column; private final int column;

View file

@ -15,68 +15,32 @@
*/ */
package ghidra.app.decompiler.component; package ghidra.app.decompiler.component;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.List; import java.util.List;
import javax.swing.JComponent;
import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager; import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.internal.PaintContext; import docking.widgets.fieldpanel.support.HighlightFactory;
import docking.widgets.fieldpanel.support.*;
import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.ClangToken;
public class ClangTextField extends WrappingVerticalLayoutTextField { public class ClangTextField extends WrappingVerticalLayoutTextField {
private List<ClangToken> tokenList; private List<ClangToken> tokenList;
private FieldElement lineNumberFieldElement; private final int lineNumber;
private static FieldElement createSingleLineElement(FieldElement[] textElements) { private static FieldElement createSingleLineElement(FieldElement[] textElements) {
return new CompositeFieldElement(textElements); return new CompositeFieldElement(textElements);
} }
/** public ClangTextField(List<ClangToken> tokenList, FieldElement[] fieldElements, int x,
* Calculates the offset of the x position for the given line number element. The line int lineNumber, int width, HighlightFactory hlFactory) {
* numbers of the decompiler appear to the left of the data and thus offset the actual data super(createSingleLineElement(fieldElements), x, width - x, 30, hlFactory, false);
* by the width of the line numbers. The line numbers may be disabled, in which case the
* given FieldElement will have no width.
*
* @param initialX The original x value passed to the constructor of this class
* @param lineNumberElement he line number element for this field from which we get a width
* @return the calculated offset
*/
private static int calculateXPositionWithLineNumberOffset(int initialX,
FieldElement lineNumberElement) {
return initialX + lineNumberElement.getStringWidth();
}
/**
* Calculates the modified width for this field. This is a factor of line numbers and any
* x offset given to this field element.
*
* @param initialX The original x value passed to the constructor of this class
* @param lineNumberElement The line number element for this field from which we get a width
* @param initialWidth The initial width we are allowed to take up
* @return the modified width for this field. This is a factor of line numbers and any
* x offset given to this field element.
*/
private static int calculateWidthFromXPosition(int initialX, FieldElement lineNumberElement,
int initialWidth) {
return initialWidth - calculateXPositionWithLineNumberOffset(initialX, lineNumberElement);
}
public ClangTextField(List<ClangToken> tokenList, FieldElement[] fieldElements,
FieldElement lineNumberFieldElement, int x, int width, HighlightFactory hlFactory) {
super(createSingleLineElement(fieldElements),
calculateXPositionWithLineNumberOffset(x, lineNumberFieldElement),
calculateWidthFromXPosition(x, lineNumberFieldElement, width), 30, hlFactory, false);
this.tokenList = tokenList; this.tokenList = tokenList;
this.lineNumberFieldElement = lineNumberFieldElement; this.lineNumber = lineNumber;
} }
/** /**
* Gets the C language token at the indicated location. * Gets the C language token at the indicated location.
*
* @param loc the field location * @param loc the field location
* @return the token * @return the token
*/ */
@ -96,11 +60,11 @@ public class ClangTextField extends WrappingVerticalLayoutTextField {
} }
/** /**
* Returns the token that is completely after the token that contains the given column * Returns the token that is completely after the token that contains the given column location.
* location. In this case, 'contains' means any position <b>inside</b> of a token, but * In this case, 'contains' means any position <b>inside</b> of a token, but not at the
* not at the beginning. So, if the column location is in the middle of a * beginning. So, if the column location is in the middle of a token, it will return the index
* token, it will return the index of next token. But if the column location is at * of next token. But if the column location is at the beginning (just before the start) of a
* the beginning (just before the start) of a token, it will return the index of that token. * token, it will return the index of that token.
* *
* @param location containing the column at which to beginning searching * @param location containing the column at which to beginning searching
* @return the next token starting after the given column * @return the next token starting after the given column
@ -156,51 +120,7 @@ public class ClangTextField extends WrappingVerticalLayoutTextField {
return tokenList; return tokenList;
} }
@Override
public void paint(JComponent c, Graphics g, PaintContext context, Rectangle clip,
FieldBackgroundColorManager selectionMap, RowColLocation cursorLoc, int rowHeight) {
// Don't print line numbers; don't copy line numbers. We are assuming that the user only
// wants to copy code.
if (context.isPrinting() || context.isTextCopying()) {
printTextWithoutLineNumbers(c, g, context, clip, selectionMap, cursorLoc, rowHeight);
return;
}
// paint our line number
lineNumberFieldElement.paint(c, g, 0, 0);
super.paint(c, g, context, clip, selectionMap, cursorLoc, rowHeight);
}
private void printTextWithoutLineNumbers(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager selectionMap, RowColLocation cursorLoc,
int rowHeight) {
int oringalStartX = startX;
try {
// strip off the line number padding...
stripLineNumbersAndLayoutText();
super.paint(c, g, context, clip, selectionMap, cursorLoc, rowHeight);
}
finally {
// ...restore the line number padding
reapplyLineNumbersAndLayoutText(oringalStartX);
}
}
private void stripLineNumbersAndLayoutText() {
startX = startX - lineNumberFieldElement.getStringWidth();
}
private void reapplyLineNumbersAndLayoutText(int originalStartX) {
startX = originalStartX;
}
public int getLineNumberWidth() {
return lineNumberFieldElement.getStringWidth();
}
public int getLineNumber() { public int getLineNumber() {
String text = lineNumberFieldElement.getText().trim(); return lineNumber;
return Integer.parseInt(text);
} }
} }

View file

@ -46,5 +46,5 @@ public interface DecompilerCallbackHandler {
void goToFunction(Function function, boolean newWindow); void goToFunction(Function function, boolean newWindow);
void doWheNotBusy(Callback c); void doWhenNotBusy(Callback c);
} }

View file

@ -33,7 +33,8 @@ import ghidra.util.bean.field.AnnotatedTextFieldElement;
import utility.function.Callback; import utility.function.Callback;
/** /**
* Coordinates the interactions between the DecompilerProvider, DecompilerPanel, and the DecompilerManager * Coordinates the interactions between the DecompilerProvider, DecompilerPanel, and the
* DecompilerManager
*/ */
public class DecompilerController { public class DecompilerController {
@ -55,7 +56,6 @@ public class DecompilerController {
new DecompilerPanel(this, options, clipboard, decompilerMgr.getTaskMonitorComponent()); new DecompilerPanel(this, options, clipboard, decompilerMgr.getTaskMonitorComponent());
decompilerPanel.setHoverMode(true); decompilerPanel.setHoverMode(true);
} }
public DecompilerPanel getDecompilerPanel() { public DecompilerPanel getDecompilerPanel() {
@ -67,8 +67,8 @@ public class DecompilerController {
//================================================================================================== //==================================================================================================
/** /**
* Called by the provider when the provider is disposed. Once dispose is called, it should * Called by the provider when the provider is disposed. Once dispose is called, it should never
* never be used again. * be used again.
*/ */
public void dispose() { public void dispose() {
clearCache(); clearCache();
@ -77,8 +77,8 @@ public class DecompilerController {
} }
/** /**
* clears all internal state and releases all resources. Called when the provider is no * clears all internal state and releases all resources. Called when the provider is no longer
* longer visible or the currently displayed program is closed. * visible or the currently displayed program is closed.
*/ */
public void clear() { public void clear() {
currentSelection = null; currentSelection = null;
@ -87,14 +87,14 @@ public class DecompilerController {
} }
/** /**
* Shows the function containing the given location in the decompilerPanel. Also, positions the * Shows the function containing the given location in the decompilerPanel. Also, positions the
* decompilerPanel's cursor to the closest equivalent position. If the decompilerPanel is * decompilerPanel's cursor to the closest equivalent position. If the decompilerPanel is
* already displaying the function, then only the cursor is repositioned. To force a * already displaying the function, then only the cursor is repositioned. To force a
* re-decompile use {@link #refreshDisplay(Program, ProgramLocation, File)}. * re-decompile use {@link #refreshDisplay(Program, ProgramLocation, File)}.
* *
* @param program the program for the given location * @param program the program for the given location
* @param location the location containing the function to be displayed and the location in * @param location the location containing the function to be displayed and the location in that
* that function to position the cursor. * function to position the cursor.
* @param viewerPosition the viewer position * @param viewerPosition the viewer position
*/ */
public void display(Program program, ProgramLocation location, ViewerPosition viewerPosition) { public void display(Program program, ProgramLocation location, ViewerPosition viewerPosition) {
@ -138,6 +138,7 @@ public class DecompilerController {
/** /**
* Sets new decompiler options and triggers a new decompile. * Sets new decompiler options and triggers a new decompile.
*
* @param decompilerOptions the options * @param decompilerOptions the options
*/ */
public void setOptions(DecompileOptions decompilerOptions) { public void setOptions(DecompileOptions decompilerOptions) {
@ -159,8 +160,8 @@ public class DecompilerController {
} }
/** /**
* Resets the native decompiler process. Call this method when the decompiler's view * Resets the native decompiler process. Call this method when the decompiler's view of a
* of a program has been invalidated, such as when a new overlay space has been added. * program has been invalidated, such as when a new overlay space has been added.
*/ */
public void resetDecompiler() { public void resetDecompiler() {
decompilerMgr.resetDecompiler(); decompilerMgr.resetDecompiler();
@ -172,6 +173,7 @@ public class DecompilerController {
/** /**
* Called by the DecompilerManager to update the currently displayed DecompileData * Called by the DecompilerManager to update the currently displayed DecompileData
*
* @param decompileData the new data * @param decompileData the new data
*/ */
public void setDecompileData(DecompileData decompileData) { public void setDecompileData(DecompileData decompileData) {
@ -199,15 +201,16 @@ public class DecompilerController {
//================================================================================================== //==================================================================================================
public void doWhenNotBusy(Callback c) { public void doWhenNotBusy(Callback c) {
callbackHandler.doWheNotBusy(c); callbackHandler.doWhenNotBusy(c);
} }
/** /**
* Always decompiles the function containing the given location before positioning the * Always decompiles the function containing the given location before positioning the
* decompilerPanel's cursor to the closest equivalent position. * decompilerPanel's cursor to the closest equivalent position.
*
* @param program the program for the given location * @param program the program for the given location
* @param location the location containing the function to be displayed and the location in * @param location the location containing the function to be displayed and the location in that
* that function to position the cursor. * function to position the cursor.
* @param debugFile the debug file * @param debugFile the debug file
*/ */
public void refreshDisplay(Program program, ProgramLocation location, File debugFile) { public void refreshDisplay(Program program, ProgramLocation location, File debugFile) {

View file

@ -39,8 +39,10 @@ import docking.widgets.fieldpanel.support.*;
import docking.widgets.indexedscrollpane.IndexedScrollPane; import docking.widgets.indexedscrollpane.IndexedScrollPane;
import ghidra.app.decompiler.*; import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.hover.DecompilerHoverService; import ghidra.app.decompiler.component.hover.DecompilerHoverService;
import ghidra.app.decompiler.component.margin.*;
import ghidra.app.plugin.core.decompile.DecompilerClipboardProvider; import ghidra.app.plugin.core.decompile.DecompilerClipboardProvider;
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation; import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
import ghidra.app.util.viewer.util.ScrollpaneAlignedHorizontalLayout;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -55,7 +57,7 @@ import ghidra.util.task.SwingUpdateManager;
* Class to handle the display of a decompiled function * Class to handle the display of a decompiled function
*/ */
public class DecompilerPanel extends JPanel implements FieldMouseListener, FieldLocationListener, public class DecompilerPanel extends JPanel implements FieldMouseListener, FieldLocationListener,
FieldSelectionListener, ClangHighlightListener { FieldSelectionListener, ClangHighlightListener, LayoutListener {
private final static Color NON_FUNCTION_BACKGROUND_COLOR_DEF = new Color(220, 220, 220); private final static Color NON_FUNCTION_BACKGROUND_COLOR_DEF = new Color(220, 220, 220);
@ -64,9 +66,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
private final DecompilerController controller; private final DecompilerController controller;
private final DecompileOptions options; private final DecompileOptions options;
private LineNumberDecompilerMarginProvider lineNumbersMargin;
private DecompilerFieldPanel fieldPanel; private final DecompilerFieldPanel fieldPanel;
private ClangLayoutController layoutMgr; private ClangLayoutController layoutMgr;
private final IndexedScrollPane scroller;
private final JComponent taskMonitorComponent;
private final List<DecompilerMarginProvider> marginProviders = new ArrayList<>();
private final VerticalLayoutPixelIndexMap pixmap = new VerticalLayoutPixelIndexMap();
private HighlightFactory hlFactory; private HighlightFactory hlFactory;
private ClangHighlightController highlightController; private ClangHighlightController highlightController;
@ -99,6 +107,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
this.controller = controller; this.controller = controller;
this.options = options; this.options = options;
this.clipboard = clipboard; this.clipboard = clipboard;
this.taskMonitorComponent = taskMonitorComponent;
FontMetrics metrics = getFontMetrics(options); FontMetrics metrics = getFontMetrics(options);
if (clipboard != null) { if (clipboard != null) {
clipboard.setFontMetrics(metrics); clipboard.setFontMetrics(metrics);
@ -109,10 +118,11 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
fieldPanel = new DecompilerFieldPanel(layoutMgr); fieldPanel = new DecompilerFieldPanel(layoutMgr);
setBackground(options.getCodeViewerBackgroundColor()); setBackground(options.getCodeViewerBackgroundColor());
IndexedScrollPane scroller = new IndexedScrollPane(fieldPanel); scroller = new IndexedScrollPane(fieldPanel);
fieldPanel.addFieldSelectionListener(this); fieldPanel.addFieldSelectionListener(this);
fieldPanel.addFieldMouseListener(this); fieldPanel.addFieldMouseListener(this);
fieldPanel.addFieldLocationListener(this); fieldPanel.addFieldLocationListener(this);
fieldPanel.addLayoutListener(this);
decompilerHoverProvider = new DecompilerHoverProvider(); decompilerHoverProvider = new DecompilerHoverProvider();
@ -127,6 +137,10 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
setPreferredSize(new Dimension(600, 400)); setPreferredSize(new Dimension(600, 400));
setDecompileData(new EmptyDecompileData("No Function")); setDecompileData(new EmptyDecompileData("No Function"));
if (options.isDisplayLineNumbers()) {
addMarginProvider(lineNumbersMargin = new LineNumberDecompilerMarginProvider());
}
} }
public List<ClangLine> getLines() { public List<ClangLine> getLines() {
@ -905,6 +919,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
} }
} }
@Override
public void layoutsChanged(List<AnchoredLayout> layouts) {
pixmap.layoutsChanged(layouts);
for (DecompilerMarginProvider element : marginProviders) {
element.setProgram(getProgram(), layoutMgr, pixmap);
}
}
private ProgramLocation getProgramLocation(Field field, FieldLocation location) { private ProgramLocation getProgramLocation(Field field, FieldLocation location) {
if (!(field instanceof ClangTextField)) { if (!(field instanceof ClangTextField)) {
return null; return null;
@ -1131,6 +1153,49 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
searchHighlightColor = decompilerOptions.getSearchHighlightColor(); searchHighlightColor = decompilerOptions.getSearchHighlightColor();
highlightController.setHighlightColor(currentVariableHighlightColor); highlightController.setHighlightColor(currentVariableHighlightColor);
if (options.isDisplayLineNumbers()) {
if (lineNumbersMargin == null) {
addMarginProvider(lineNumbersMargin = new LineNumberDecompilerMarginProvider());
}
}
else {
if (lineNumbersMargin != null) {
removeMarginProvider(lineNumbersMargin);
lineNumbersMargin = null;
}
}
for (DecompilerMarginProvider element : marginProviders) {
element.setOptions(options);
}
}
public void addMarginProvider(DecompilerMarginProvider provider) {
marginProviders.add(provider);
provider.setOptions(options);
provider.setProgram(getProgram(), layoutMgr, pixmap);
buildPanels();
}
public void removeMarginProvider(DecompilerMarginProvider provider) {
marginProviders.remove(provider);
buildPanels();
}
private void buildPanels() {
removeAll();
add(buildLeftComponent(), BorderLayout.WEST);
add(scroller, BorderLayout.CENTER);
add(taskMonitorComponent, BorderLayout.SOUTH);
}
private JComponent buildLeftComponent() {
JPanel leftPanel = new JPanel(new ScrollpaneAlignedHorizontalLayout(scroller));
for (DecompilerMarginProvider marginProvider : marginProviders) {
leftPanel.add(marginProvider.getComponent());
}
return leftPanel;
} }
//================================================================================================== //==================================================================================================

View file

@ -0,0 +1,92 @@
/* ###
* 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.decompiler.component.margin;
import java.awt.Component;
import java.math.BigInteger;
import javax.swing.JComponent;
import docking.widgets.fieldpanel.LayoutModel;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.DecompilerMarginService;
import ghidra.program.model.listing.Program;
/**
* A provider of a margin Swing component
*
* <p>
* To add a margin to the decompiler, a client must implement this interface to provide the
* component that is actually added to the UI. For a reference implementation, see
* {@link LineNumberDecompilerMarginProvider}.
*/
public interface DecompilerMarginProvider {
/**
* Called whenever the program, function, or layout changes
*
* <p>
* The implementation should keep a reference at least to the {@code model} and the
* {@code pixmap} for later use during painting. The model provides access to the lines of
* decompiler C code. Each layout corresponds to a single line of C code. For example, the first
* line of code is rendered by the layout at index 0. The tenth is rendered by the layout at
* index 9. Rarely, a line may be wrapped by the renderer, leading to a non-uniform layout. The
* {@code pixmap} can map from a pixel's vertical position to the layout index at the same
* position in the main panel. It accounts for scrolling an non-uniformity. It is safe to assume
* the layouts render contiguous lines of C code. The recommended strategy for painting is thus:
*
* <ol>
* <li>Compute the visible part of the margin needing repainting. See
* {@link JComponent#getVisibleRect()}</li>
* <li>Compute the layout indices for the vertical bounds of that part. See
* {@link LayoutPixelIndexMap#getIndex(int)}</li>
* <li>Iterate over the layouts within those bounds, inclusively.</li>
* <li>Compute the vertical position of each layout and paint something appropriate for its
* corresponding line. See {@link LayoutPixelIndexMap#getPixel(BigInteger)}</li>
* </ol>
*
* <p>
* A call to this method should cause the component to be repainted.
*
* @param program the program for the current function
* @param model the line/token model
* @param pixmap a map from pixels' y coordinates to layout index, i.e, line number
*/
void setProgram(Program program, LayoutModel model, LayoutPixelIndexMap pixmap);
/**
* Get the Swing component implementing the actual margin, often {@code this}
*
* @return the component
*/
Component getComponent();
/**
* Set the options for the margin
*
* <p>
* This is called at least once when the provider is added to the margin service. See
* {@link DecompilerMarginService#addMarginProvider(DecompilerMarginProvider)}. It subsequently
* called whenever a decompiler option changes. To receive other options, the provider will need
* to listen using its own mechanism.
*
* <p>
* A call to this method should cause the component to be repainted. Implementors may choose to
* repaint only when certain options change.
*/
default void setOptions(DecompileOptions options) {
}
}

View file

@ -0,0 +1,63 @@
/* ###
* 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.decompiler.component.margin;
import java.math.BigInteger;
import docking.widgets.fieldpanel.LayoutModel;
import ghidra.program.model.listing.Program;
/**
* A mapping from pixel coordinate to layout index
*
* <p>
* At the moment, the only implementation provides a map from vertical position to layout. While
* this does not have to be the case, the documentation will presume the y coordinate.
*/
public interface LayoutPixelIndexMap {
/**
* Get the top of the layout with the given index
*
* <p>
* Gets the minimum y coordinate of any pixel occupied by the layout having the given index. In
* essence, this maps from layout index to vertical position, relative to the main panel's
* viewport. This accounts for scrolling and non-uniform height among the layouts.
*
* @param index the index of the layout
* @return the top of the layout, relative to the main panel's viewport
*/
int getPixel(BigInteger index);
/**
* Get the index of the layout at the given position
*
* <p>
* Get the index of the layout occupying the line of pixels in the main panel having the given y
* coordinate. In essence, this maps from vertical position, relative to the main panel's
* viewport, to layout index. This accounts for scrolling and non-uniform height among the
* layouts.
*
* @implNote Clients should avoid frequent calls to this method. Even though it can be
* implemented easily in log time, an invocation for every pixel or line of pixels
* painted could still be unnecessarily expensive. It should only be necessary to call
* this once or twice per repaint. See
* {@link DecompilerMarginProvider#setProgram(Program, LayoutModel, LayoutPixelIndexMap)}.
*
* @param pixel the vertical position of the pixel, relative to the main panel's viewport
* @return the index of the layout
*/
BigInteger getIndex(int pixel);
}

View file

@ -0,0 +1,106 @@
/* ###
* 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.decompiler.component.margin;
import java.awt.*;
import java.math.BigInteger;
import javax.swing.JPanel;
import docking.util.GraphicsUtils;
import docking.widgets.fieldpanel.LayoutModel;
import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.program.model.listing.Program;
/**
* The built-in provider for the Decompiler's line number margin
*/
public class LineNumberDecompilerMarginProvider extends JPanel
implements DecompilerMarginProvider, LayoutModelListener {
private LayoutPixelIndexMap pixmap;
private LayoutModel model;
@Override
public void setProgram(Program program, LayoutModel model, LayoutPixelIndexMap pixmap) {
setLayoutManager(model);
this.pixmap = pixmap;
repaint();
}
private void setLayoutManager(LayoutModel model) {
if (this.model == model) {
return;
}
if (this.model != null) {
this.model.removeLayoutModelListener(this);
}
this.model = model;
this.model.addLayoutModelListener(this);
setWidthForLastLine();
if (this.model != null) {
this.model.addLayoutModelListener(this);
}
}
@Override
public void setOptions(DecompileOptions options) {
this.setFont(options.getDefaultFont());
setWidthForLastLine();
repaint();
}
@Override
public Component getComponent() {
return this;
}
@Override
public void modelSizeChanged(IndexMapper indexMapper) {
setWidthForLastLine();
repaint();
}
@Override
public void dataChanged(BigInteger start, BigInteger end) {
repaint();
}
private void setWidthForLastLine() {
if (model == null) {
return;
}
int lastLine = model.getNumIndexes().intValueExact();
int width = getFontMetrics(getFont()).stringWidth(Integer.toString(lastLine));
setPreferredSize(new Dimension(width, 0));
invalidate();
}
@Override
public void paint(Graphics g) {
super.paint(g);
Rectangle visible = getVisibleRect();
BigInteger startIdx = pixmap.getIndex(visible.y);
BigInteger endIdx = pixmap.getIndex(visible.y + visible.height);
int ascent = g.getFontMetrics().getMaxAscent();
for (BigInteger i = startIdx; i.compareTo(endIdx) <= 0; i = i.add(BigInteger.ONE)) {
GraphicsUtils.drawString(this, g, i.add(BigInteger.ONE).toString(), 0,
pixmap.getPixel(i) + ascent);
}
}
}

View file

@ -0,0 +1,71 @@
/* ###
* 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.decompiler.component.margin;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import docking.widgets.fieldpanel.support.AnchoredLayout;
/**
* An implementation of {@link LayoutPixelIndexMap} for vertical coordinates
*
* <p>
* This class implements {@link #getIndex(int)} in log time and {@link #getPixel(BigInteger)} in
* constant time.
*/
public class VerticalLayoutPixelIndexMap implements LayoutPixelIndexMap {
private BigInteger base = BigInteger.ZERO;
private int[] yPositions = new int[0];
private int size;
@Override
public int getPixel(BigInteger index) {
return yPositions[index.subtract(base).intValueExact()];
}
protected int computeOff(int pixel) {
int result = Arrays.binarySearch(yPositions, 0, size, pixel);
if (result >= 0) {
return result;
}
// result = -insertionPoint - 1, first index where acc[index] > pixel
// want index = insertionPoint - 1, index where acc[index] < pixel < acc[index+1]
// insertionPoint = -result - 1
// index = -result - 2;
return -result - 2;
}
@Override
public BigInteger getIndex(int pixel) {
return base.add(BigInteger.valueOf(computeOff(pixel)));
}
public void layoutsChanged(List<AnchoredLayout> layouts) {
size = layouts.size();
if (yPositions.length < size) {
yPositions = new int[size];
}
int i = 0;
base = layouts.isEmpty() ? BigInteger.ZERO : layouts.get(0).getIndex();
for (AnchoredLayout l : layouts) {
assert l.getIndex().subtract(base).intValueExact() == i;
yPositions[i] = l.getYPos();
i++;
}
}
}

View file

@ -20,8 +20,7 @@ import java.util.*;
import org.jdom.Element; import org.jdom.Element;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.*;
import ghidra.app.decompiler.DecompilerHighlightService;
import ghidra.app.decompiler.component.hover.DecompilerHoverService; import ghidra.app.decompiler.component.hover.DecompilerHoverService;
import ghidra.app.events.*; import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
@ -40,7 +39,6 @@ import ghidra.util.task.SwingUpdateManager;
/** /**
* Plugin for producing a high-level C interpretation of assembly functions. * Plugin for producing a high-level C interpretation of assembly functions.
*/ */
//@formatter:off
@PluginInfo( @PluginInfo(
status = PluginStatus.RELEASED, status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME, packageName = CorePluginPackage.NAME,
@ -56,9 +54,7 @@ import ghidra.util.task.SwingUpdateManager;
ProgramActivatedPluginEvent.class, ProgramOpenedPluginEvent.class, ProgramActivatedPluginEvent.class, ProgramOpenedPluginEvent.class,
ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class, ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class,
ProgramClosedPluginEvent.class ProgramClosedPluginEvent.class
} })
)
//@formatter:on
public class DecompilePlugin extends Plugin { public class DecompilePlugin extends Plugin {
private PrimaryDecompilerProvider connectedProvider; private PrimaryDecompilerProvider connectedProvider;
@ -69,9 +65,8 @@ public class DecompilePlugin extends Plugin {
private ProgramSelection currentSelection; private ProgramSelection currentSelection;
/** /**
* Delay location changes to allow location events to settle down. * Delay location changes to allow location events to settle down. This happens when a
* This happens when a readDataState occurs when a tool is restored * readDataState occurs when a tool is restored or when switching program tabs.
* or when switching program tabs.
*/ */
SwingUpdateManager delayedLocationUpdateMgr = new SwingUpdateManager(200, 200, () -> { SwingUpdateManager delayedLocationUpdateMgr = new SwingUpdateManager(200, 200, () -> {
if (currentLocation == null) { if (currentLocation == null) {
@ -96,6 +91,8 @@ public class DecompilePlugin extends Plugin {
private void registerServices() { private void registerServices() {
registerServiceProvided(DecompilerHighlightService.class, connectedProvider); registerServiceProvided(DecompilerHighlightService.class, connectedProvider);
// Allow pluggable margin providers for disconnected providers?
registerServiceProvided(DecompilerMarginService.class, connectedProvider);
} }
@Override @Override

View file

@ -209,7 +209,7 @@ public class DecompilerClipboardProvider extends ByteCopier
LayoutModel model = provider.getDecompilerPanel().getLayoutModel(); LayoutModel model = provider.getDecompilerPanel().getLayoutModel();
Layout layout = model.getLayout(BigInteger.valueOf(lineNumber)); Layout layout = model.getLayout(BigInteger.valueOf(lineNumber));
ClangTextField field = (ClangTextField) layout.getField(0); ClangTextField field = (ClangTextField) layout.getField(0);
int numSpaces = (field.getStartX() - field.getLineNumberWidth()) / spaceCharWidthInPixels; int numSpaces = field.getStartX() / spaceCharWidthInPixels;
for (int i = 0; i < numSpaces; i++) { for (int i = 0; i < numSpaces; i++) {
buffer.append(' '); buffer.append(' ');
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -30,6 +30,7 @@ import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.GhidraOptions; import ghidra.GhidraOptions;
import ghidra.app.decompiler.*; import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.*; import ghidra.app.decompiler.component.*;
import ghidra.app.decompiler.component.margin.DecompilerMarginProvider;
import ghidra.app.nav.*; import ghidra.app.nav.*;
import ghidra.app.plugin.core.decompile.actions.*; import ghidra.app.plugin.core.decompile.actions.*;
import ghidra.app.services.*; import ghidra.app.services.*;
@ -55,7 +56,7 @@ import utility.function.Callback;
public class DecompilerProvider extends NavigatableComponentProviderAdapter public class DecompilerProvider extends NavigatableComponentProviderAdapter
implements DomainObjectListener, OptionsChangeListener, DecompilerCallbackHandler, implements DomainObjectListener, OptionsChangeListener, DecompilerCallbackHandler,
DecompilerHighlightService { DecompilerHighlightService, DecompilerMarginService {
private static final String OPTIONS_TITLE = "Decompiler"; private static final String OPTIONS_TITLE = "Decompiler";
@ -385,6 +386,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
/** /**
* Sets the current program and adds/removes itself as a domainObjectListener * Sets the current program and adds/removes itself as a domainObjectListener
*
* @param newProgram the new program or null to clear out the current program. * @param newProgram the new program or null to clear out the current program.
*/ */
void doSetProgram(Program newProgram) { void doSetProgram(Program newProgram) {
@ -422,9 +424,10 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
} }
/** /**
* sets the current location for this provider. If the provider is not visible, it does not * sets the current location for this provider. If the provider is not visible, it does not pass
* pass it on to the controller. When the component is later shown, the current location * it on to the controller. When the component is later shown, the current location will then be
* will then be passed to the controller. * passed to the controller.
*
* @param loc the location to compile and set the cursor. * @param loc the location to compile and set the cursor.
* @param viewerPosition if non-null the position at which to scroll the view. * @param viewerPosition if non-null the position at which to scroll the view.
*/ */
@ -471,8 +474,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
} }
/** /**
* Returns a string that shows the current line with the field under the cursor in between * Returns a string that shows the current line with the field under the cursor in between '[]'
* '[]' chars. * chars.
* *
* @return the string * @return the string
*/ */
@ -648,7 +651,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
} }
@Override @Override
public void doWheNotBusy(Callback c) { public void doWhenNotBusy(Callback c) {
followUpWork.offer(c); followUpWork.offer(c);
followUpWorkUpdater.update(); followUpWorkUpdater.update();
} }
@ -678,7 +681,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
// transfer any state after the new decompiler is initialized // transfer any state after the new decompiler is initialized
DecompilerPanel newPanel = newProvider.getDecompilerPanel(); DecompilerPanel newPanel = newProvider.getDecompilerPanel();
newProvider.doWheNotBusy(() -> { newProvider.doWhenNotBusy(() -> {
newPanel.setViewerPosition(myViewPosition); newPanel.setViewerPosition(myViewPosition);
newPanel.cloneHighlights(myPanel); newPanel.cloneHighlights(myPanel);
}); });
@ -1113,4 +1116,14 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
void handleTokenRenamed(ClangToken tokenAtCursor, String newName) { void handleTokenRenamed(ClangToken tokenAtCursor, String newName) {
controller.getDecompilerPanel().tokenRenamed(tokenAtCursor, newName); controller.getDecompilerPanel().tokenRenamed(tokenAtCursor, newName);
} }
@Override
public void addMarginProvider(DecompilerMarginProvider provider) {
getDecompilerPanel().addMarginProvider(provider);
}
@Override
public void removeMarginProvider(DecompilerMarginProvider provider) {
getDecompilerPanel().removeMarginProvider(provider);
}
} }

View file

@ -310,7 +310,7 @@ public class ConvertConstantTask implements Callback {
catch (InterruptedException e) { catch (InterruptedException e) {
return; return;
} }
context.getComponentProvider().doWheNotBusy(this); context.getComponentProvider().doWhenNotBusy(this);
} }
else { else {
applyPrimaryEquate(); applyPrimaryEquate();

View file

@ -25,7 +25,7 @@ import docking.widgets.fieldpanel.listener.LayoutModelListener;
* using a BigFieldPanel. * using a BigFieldPanel.
*/ */
public interface LayoutModel { public interface LayoutModel extends Iterable<Layout> {
/** /**
* Returns true if every index returns a non-null layout and all the layouts * Returns true if every index returns a non-null layout and all the layouts
@ -68,6 +68,7 @@ public interface LayoutModel {
* *
* @return new iterator * @return new iterator
*/ */
@Override
public default LayoutModelIterator iterator() { public default LayoutModelIterator iterator() {
return new LayoutModelIterator(this); return new LayoutModelIterator(this);
} }