mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-30 16:47:43 +08:00
Merge remote-tracking branch 'origin/GP-6751-dragonmacher-decompiler-highlights'
This commit is contained in:
+2
-3
@@ -241,9 +241,8 @@ public class DecompilerCodeComparisonView extends CodeComparisonView {
|
|||||||
private void linkHighlightControllers() {
|
private void linkHighlightControllers() {
|
||||||
DiffClangHighlightController left = cDisplays.get(LEFT).getHighlightController();
|
DiffClangHighlightController left = cDisplays.get(LEFT).getHighlightController();
|
||||||
DiffClangHighlightController right = cDisplays.get(RIGHT).getHighlightController();
|
DiffClangHighlightController right = cDisplays.get(RIGHT).getHighlightController();
|
||||||
left.addListener(right);
|
left.setTokenChangedListener(right);
|
||||||
right.addListener(left);
|
right.setTokenChangedListener(left);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+189
-142
@@ -17,18 +17,16 @@ package ghidra.features.codecompare.decompile;
|
|||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
import docking.widgets.EventTrigger;
|
import docking.widgets.EventTrigger;
|
||||||
import docking.widgets.fieldpanel.field.Field;
|
import docking.widgets.fieldpanel.field.Field;
|
||||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||||
import ghidra.app.decompiler.ClangSyntaxToken;
|
import ghidra.app.decompiler.*;
|
||||||
import ghidra.app.decompiler.ClangToken;
|
|
||||||
import ghidra.app.decompiler.component.*;
|
import ghidra.app.decompiler.component.*;
|
||||||
import ghidra.features.codecompare.graphanalysis.TokenBin;
|
import ghidra.features.codecompare.graphanalysis.TokenBin;
|
||||||
import ghidra.util.ColorUtils;
|
|
||||||
import ghidra.util.SystemUtilities;
|
|
||||||
import util.CollectionUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to handle Function Difference highlights for a decompiled function.
|
* Class to handle Function Difference highlights for a decompiled function.
|
||||||
@@ -40,202 +38,251 @@ public class DiffClangHighlightController extends LocationClangHighlightControll
|
|||||||
private Set<ClangToken> diffTokenSet = new HashSet<>();
|
private Set<ClangToken> diffTokenSet = new HashSet<>();
|
||||||
private ClangToken locationToken;
|
private ClangToken locationToken;
|
||||||
private TokenBin locationTokenBin;
|
private TokenBin locationTokenBin;
|
||||||
private List<TokenBin> highlightBins;
|
private List<TokenBin> allTokenBins;
|
||||||
private List<DiffClangHighlightListener> listenerList = new ArrayList<>();
|
|
||||||
private TokenBin matchingTokenBin;
|
|
||||||
private DecompilerCodeComparisonOptions comparisonOptions;
|
private DecompilerCodeComparisonOptions comparisonOptions;
|
||||||
|
private DiffClangHighlightListener listener = new DummyListener();
|
||||||
|
|
||||||
|
private DiffTokenHighlighter diffColorHighlighter;
|
||||||
|
private BasicTokenHighlighter currentTokenHighlighter;
|
||||||
|
|
||||||
|
// highlights the token in this highlighter for the selected token in the other highlighter
|
||||||
|
private BasicTokenHighlighter matchingTokenHighlighter;
|
||||||
|
|
||||||
public DiffClangHighlightController(DecompilerCodeComparisonOptions comparisonOptions) {
|
public DiffClangHighlightController(DecompilerCodeComparisonOptions comparisonOptions) {
|
||||||
this.comparisonOptions = comparisonOptions;
|
this.comparisonOptions = comparisonOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearDiffHighlights() {
|
|
||||||
doClearDiffHighlights();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doClearDiffHighlights() {
|
|
||||||
ClangToken[] array = diffTokenSet.toArray(new ClangToken[diffTokenSet.size()]);
|
|
||||||
for (ClangToken clangToken : array) {
|
|
||||||
clearDiffHighlight(clangToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearDiffHighlight(ClangToken clangToken) {
|
|
||||||
Color highlight = clangToken.getHighlight();
|
|
||||||
if (highlight != null && highlight.equals(comparisonOptions.getDiffHighlightColor())) {
|
|
||||||
clangToken.setHighlight(null);
|
|
||||||
}
|
|
||||||
diffTokenSet.remove(clangToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearNonDiffHighlight(ClangToken clangToken) {
|
|
||||||
if (diffTokenSet.contains(clangToken)) {
|
|
||||||
clangToken.setHighlight(comparisonOptions.getDiffHighlightColor());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clangToken.setHighlight(null);
|
|
||||||
}
|
|
||||||
if (clangToken.isMatchingToken()) {
|
|
||||||
clangToken.setMatchingToken(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDiffHighlights(List<TokenBin> highlightBins, Set<ClangToken> tokenSet) {
|
public void setDiffHighlights(List<TokenBin> highlightBins, Set<ClangToken> tokenSet) {
|
||||||
this.highlightBins = highlightBins;
|
this.allTokenBins = highlightBins;
|
||||||
doClearDiffHighlights();
|
|
||||||
for (ClangToken clangToken : tokenSet) {
|
clearDiffHighlights();
|
||||||
clangToken.setHighlight(comparisonOptions.getDiffHighlightColor());
|
|
||||||
diffTokenSet.add(clangToken);
|
if (!tokenSet.isEmpty()) {
|
||||||
|
Color color = comparisonOptions.getDiffHighlightColor();
|
||||||
|
diffColorHighlighter = new DiffTokenHighlighter(new ArrayList<>(tokenSet), color);
|
||||||
|
diffColorHighlighter.applyHighlights();
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fieldLocationChanged(FieldLocation location, Field field, EventTrigger trigger) {
|
public void fieldLocationChanged(FieldLocation location, Field field,
|
||||||
|
EventTrigger trigger) {
|
||||||
|
|
||||||
if (!(field instanceof ClangTextField)) {
|
if (!(field instanceof ClangTextField textField)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the token for the location so we can highlight its token bin.
|
// Get the token for the location so we can highlight its token bin. Also we will use it
|
||||||
// Also we will use it when notifying the other panel to highlight.
|
// when notifying the other panel to highlight.
|
||||||
ClangToken tok = ((ClangTextField) field).getToken(location);
|
ClangToken tok = textField.getToken(location);
|
||||||
if (SystemUtilities.isEqual(locationToken, tok)) {
|
if (Objects.equals(locationToken, tok)) {
|
||||||
return; // Current location's token hasn't changed.
|
return; // current location's token hasn't changed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undo any highlight of the previous matching tokenBin.
|
|
||||||
if (matchingTokenBin != null && matchingTokenBin.getMatch() != null) {
|
|
||||||
clearTokenBinHighlight(matchingTokenBin.getMatch());
|
|
||||||
matchingTokenBin = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearCurrentLocationHighlight();
|
|
||||||
|
|
||||||
clearPrimaryHighlights();
|
clearPrimaryHighlights();
|
||||||
addPrimaryHighlight(tok, defaultHighlightColor);
|
clearCurrentLocationHighlight();
|
||||||
if (tok instanceof ClangSyntaxToken) {
|
clearMatchingTokenBin();
|
||||||
List<ClangToken> tokens = addPrimaryHighlightToTokensForParenthesis(
|
|
||||||
(ClangSyntaxToken) tok, defaultParenColor);
|
highlightTokensBetweenParens(tok);
|
||||||
reHighlightDiffs(tokens);
|
highlightCurrentLocationToken(tok);
|
||||||
addPrimaryHighlightToTokensForBrace((ClangSyntaxToken) tok, defaultParenColor);
|
|
||||||
|
listener.locationTokenChanged(locationTokenBin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void highlightTokensBetweenParens(ClangToken tok) {
|
||||||
|
|
||||||
|
if (!(tok instanceof ClangSyntaxToken syntaxToken)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPrimaryHighlightToTokensForParenthesis(syntaxToken, defaultParenColor);
|
||||||
|
addPrimaryHighlightToTokensForBrace(syntaxToken, defaultParenColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void highlightCurrentLocationToken(ClangToken tok) {
|
||||||
|
|
||||||
TokenBin tokenBin = null;
|
TokenBin tokenBin = null;
|
||||||
if (tok != null) {
|
if (tok != null && allTokenBins != null) {
|
||||||
Color highlightColor = comparisonOptions.getFocusedTokenIneligibleHighlightColor(); // Don't know
|
tokenBin = TokenBin.getBinContainingToken(allTokenBins, tok);
|
||||||
if (highlightBins != null) {
|
}
|
||||||
tokenBin = TokenBin.getBinContainingToken(highlightBins, tok);
|
|
||||||
|
Color binHlColor = comparisonOptions.getFocusedTokenIneligibleHighlightColor();
|
||||||
if (tokenBin != null) {
|
if (tokenBin != null) {
|
||||||
if (tokenBin.getMatch() != null) {
|
if (tokenBin.getMatch() != null) {
|
||||||
highlightColor = comparisonOptions.getFocusedTokenMatchHighlightColor();
|
binHlColor = comparisonOptions.getFocusedTokenMatchHighlightColor();
|
||||||
}
|
|
||||||
else if (tokenBin.getMatch() == null) {
|
|
||||||
highlightColor = comparisonOptions.getFocusedTokenUnmatchedHighlightColor();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// All the tokens that didn't fall into the "has a match" or "no match"
|
binHlColor = comparisonOptions.getFocusedTokenUnmatchedHighlightColor();
|
||||||
// categories above are in a single token bin.
|
|
||||||
// We don't want all these highlighted at the same time, so set the
|
|
||||||
// tokenBin to null. By doing this, only the current token gets highlighted.
|
|
||||||
tokenBin = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
locationToken = tok;
|
locationToken = tok;
|
||||||
locationTokenBin = tokenBin;
|
locationTokenBin = tokenBin;
|
||||||
if (tokenBin == null) {
|
|
||||||
addPrimaryHighlight(tok, highlightColor);
|
List<ClangToken> tokens = List.of();
|
||||||
|
if (tokenBin != null) {
|
||||||
|
tokens = toList(tokenBin);
|
||||||
}
|
}
|
||||||
else {
|
else if (tok != null) {
|
||||||
addTokenBinHighlight(tokenBin, highlightColor);
|
tokens = List.of(tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
installCurrentTokenHighlighter(tokens, binHlColor);
|
||||||
|
refreshDiffHighlightsForCurrentLocationChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The diff highlighter is smart enough to ignore the token at the current location. We have to
|
||||||
|
* kick it when the location changes so it will update the highlights.
|
||||||
|
*/
|
||||||
|
private void refreshDiffHighlightsForCurrentLocationChange() {
|
||||||
|
if (diffColorHighlighter != null) {
|
||||||
|
diffColorHighlighter.clearHighlights();
|
||||||
|
diffColorHighlighter.applyHighlights();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify other decompiler panel highlight controller we have a new location token.
|
private static List<ClangToken> toList(TokenBin tokens) {
|
||||||
for (DiffClangHighlightListener listener : listenerList) {
|
return StreamSupport.stream(tokens.spliterator(), false).collect(Collectors.toList());
|
||||||
listener.locationTokenChanged(tok, tokenBin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reHighlightDiffs(List<ClangToken> tokenList) {
|
|
||||||
Color averageColor =
|
|
||||||
ColorUtils.blend(defaultParenColor, comparisonOptions.getDiffHighlightColor(), 0.5);
|
|
||||||
for (ClangToken clangToken : tokenList) {
|
|
||||||
if (diffTokenSet.contains(clangToken)) {
|
|
||||||
clangToken.setHighlight(averageColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearCurrentLocationHighlight() {
|
private void clearCurrentLocationHighlight() {
|
||||||
if (locationTokenBin != null) {
|
|
||||||
clearTokenBinHighlight(locationTokenBin);
|
if (currentTokenHighlighter != null) {
|
||||||
|
currentTokenHighlighter.dispose();
|
||||||
|
currentTokenHighlighter = null;
|
||||||
|
}
|
||||||
|
|
||||||
locationTokenBin = null;
|
locationTokenBin = null;
|
||||||
locationToken = null;
|
locationToken = null;
|
||||||
}
|
}
|
||||||
if (locationToken != null) {
|
|
||||||
clearNonDiffHighlight(locationToken);
|
private void clearDiffHighlights() {
|
||||||
locationToken = null;
|
|
||||||
|
if (diffColorHighlighter != null) {
|
||||||
|
diffColorHighlighter.dispose();
|
||||||
|
diffColorHighlighter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
diffTokenSet.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearMatchingTokenBin() {
|
||||||
|
if (matchingTokenHighlighter != null) {
|
||||||
|
matchingTokenHighlighter.dispose();
|
||||||
|
matchingTokenHighlighter = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTokenBinHighlight(TokenBin tokenBin, Color highlightColor) {
|
private void installCurrentTokenHighlighter(List<ClangToken> tokens, Color highlightColor) {
|
||||||
for (ClangToken token : tokenBin) {
|
if (tokens.isEmpty()) {
|
||||||
addPrimaryHighlight(token, highlightColor);
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearTokenBinHighlight(TokenBin tokenBin) {
|
currentTokenHighlighter = new BasicTokenHighlighter(tokens, highlightColor);
|
||||||
for (ClangToken token : tokenBin) {
|
currentTokenHighlighter.applyHighlights();
|
||||||
clearNonDiffHighlight(token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doClearHighlights(TokenHighlights tokens) {
|
private void installMatchingTokenBinHighlighter(TokenBin tokenBin, Color highlightColor) {
|
||||||
List<ClangToken> clangTokens =
|
|
||||||
CollectionUtils.asStream(tokens).map(ht -> ht.getToken()).collect(Collectors.toList());
|
clearMatchingTokenBin();
|
||||||
for (ClangToken clangToken : clangTokens) {
|
|
||||||
clearNonDiffHighlight(clangToken);
|
if (tokenBin == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
tokens.clear();
|
|
||||||
notifyListeners();
|
matchingTokenHighlighter = new BasicTokenHighlighter(tokenBin, highlightColor);
|
||||||
|
matchingTokenHighlighter.applyHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTokenChangedListener(DiffClangHighlightListener listener) {
|
||||||
|
this.listener = listener == null ? new DummyListener() : listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearPrimaryHighlights() {
|
public void locationTokenChanged(TokenBin tokenBin) {
|
||||||
doClearHighlights(getPrimaryHighlights());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean addListener(DiffClangHighlightListener listener) {
|
|
||||||
return listenerList.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean removeListener(DiffClangHighlightListener listener) {
|
|
||||||
return listenerList.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void locationTokenChanged(ClangToken tok, TokenBin tokenBin) {
|
|
||||||
clearCurrentLocationHighlight();
|
clearCurrentLocationHighlight();
|
||||||
|
refreshDiffHighlightsForCurrentLocationChange();
|
||||||
|
|
||||||
// The token Changed in our other matching DiffClangHighlightController
|
// The token Changed in our other matching DiffClangHighlightController
|
||||||
highlightMatchingToken(tok, tokenBin);
|
if (tokenBin != null) {
|
||||||
|
TokenBin match = tokenBin.getMatch();
|
||||||
|
Color color = comparisonOptions.getFocusedTokenMatchHighlightColor();
|
||||||
|
installMatchingTokenBinHighlighter(match, color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void highlightMatchingToken(ClangToken tok, TokenBin tokenBin) {
|
//=================================================================================================
|
||||||
// Undo any highlight of the previous matching tokenBin.
|
// Inner Classes
|
||||||
if (matchingTokenBin != null && matchingTokenBin.getMatch() != null) {
|
//=================================================================================================
|
||||||
clearTokenBinHighlight(matchingTokenBin.getMatch());
|
|
||||||
|
/**
|
||||||
|
* Highlights a given set of tokens with the given color.
|
||||||
|
*/
|
||||||
|
private class BasicTokenHighlighter implements DecompilerHighlighter {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
protected List<ClangToken> tokens;
|
||||||
|
private Color color;
|
||||||
|
|
||||||
|
BasicTokenHighlighter(TokenBin tokens, Color color) {
|
||||||
|
this(toList(tokens), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight the new matching tokenBin.
|
BasicTokenHighlighter(List<ClangToken> tokens, Color color) {
|
||||||
if (tokenBin != null && tokenBin.getMatch() != null) {
|
this.color = color;
|
||||||
addTokenBinHighlight(tokenBin.getMatch(),
|
UUID uuId = UUID.randomUUID();
|
||||||
comparisonOptions.getFocusedTokenMatchHighlightColor());
|
this.id = uuId.toString();
|
||||||
|
this.tokens = tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingTokenBin = tokenBin;
|
// subclass overrides this method
|
||||||
|
protected List<ClangToken> getCurrentTokens() {
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyHighlights() {
|
||||||
|
Supplier<? extends Collection<ClangToken>> tokenSupplier = this::getCurrentTokens;
|
||||||
|
ColorProvider cp = t -> color;
|
||||||
|
addHighlighterHighlights(this, tokenSupplier, cp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHighlights() {
|
||||||
|
removeHighlighterHighlights(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
removeHighlighterHighlights(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DiffTokenHighlighter extends BasicTokenHighlighter {
|
||||||
|
|
||||||
|
DiffTokenHighlighter(List<ClangToken> tokens, Color color) {
|
||||||
|
super(tokens, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<ClangToken> getCurrentTokens() {
|
||||||
|
// ignore the selected token so that it paints with its own color and does not get
|
||||||
|
// blended with this highlighter's color
|
||||||
|
return tokens.stream().filter(t -> t != locationToken).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DummyListener implements DiffClangHighlightListener {
|
||||||
|
@Override
|
||||||
|
public void locationTokenChanged(TokenBin tokenBin) {
|
||||||
|
// stub
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -15,16 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.features.codecompare.decompile;
|
package ghidra.features.codecompare.decompile;
|
||||||
|
|
||||||
import ghidra.app.decompiler.ClangToken;
|
|
||||||
import ghidra.features.codecompare.graphanalysis.TokenBin;
|
import ghidra.features.codecompare.graphanalysis.TokenBin;
|
||||||
|
|
||||||
public interface DiffClangHighlightListener {
|
public interface DiffClangHighlightListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifier that the current location changed to the specified token.
|
* Notifier that the current location changed to the specified token.
|
||||||
* @param tok the token
|
|
||||||
* @param tokenBin the bin which contains the token. Otherwise, null.
|
* @param tokenBin the bin which contains the token. Otherwise, null.
|
||||||
*/
|
*/
|
||||||
public void locationTokenChanged(ClangToken tok, TokenBin tokenBin);
|
public void locationTokenChanged(TokenBin tokenBin);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ public class ClangCommentToken extends ClangToken {
|
|||||||
newToken.setText(text);
|
newToken.setText(text);
|
||||||
newToken.setLineParent(source.getLineParent());
|
newToken.setLineParent(source.getLineParent());
|
||||||
newToken.setSyntaxType(source.getSyntaxType());
|
newToken.setSyntaxType(source.getSyntaxType());
|
||||||
newToken.setHighlight(source.getHighlight());
|
|
||||||
newToken.srcaddr = source.srcaddr;
|
newToken.srcaddr = source.srcaddr;
|
||||||
return newToken;
|
return newToken;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.decompiler;
|
package ghidra.app.decompiler;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
@@ -45,12 +44,6 @@ public interface ClangNode {
|
|||||||
*/
|
*/
|
||||||
public Address getMaxAddress();
|
public Address getMaxAddress();
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a highlighting background color for all text elements
|
|
||||||
* @param c is the color to set
|
|
||||||
*/
|
|
||||||
public void setHighlight(Color c);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the number of immediate groupings this text breaks up into
|
* Return the number of immediate groupings this text breaks up into
|
||||||
* @return the number of child groupings
|
* @return the number of child groupings
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package ghidra.app.decompiler;
|
|||||||
import static ghidra.program.model.pcode.AttributeId.*;
|
import static ghidra.program.model.pcode.AttributeId.*;
|
||||||
import static ghidra.program.model.pcode.ElementId.*;
|
import static ghidra.program.model.pcode.ElementId.*;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -49,13 +48,11 @@ public class ClangToken implements ClangNode {
|
|||||||
private ClangLine lineparent;
|
private ClangLine lineparent;
|
||||||
private String text;
|
private String text;
|
||||||
private int syntax_type;
|
private int syntax_type;
|
||||||
private Color highlight; // Color to highlight with or null if no highlight
|
|
||||||
private boolean matchingToken;
|
private boolean matchingToken;
|
||||||
|
|
||||||
public ClangToken(ClangNode par) {
|
public ClangToken(ClangNode par) {
|
||||||
parent = par;
|
parent = par;
|
||||||
text = null;
|
text = null;
|
||||||
highlight = null;
|
|
||||||
syntax_type = DEFAULT_COLOR;
|
syntax_type = DEFAULT_COLOR;
|
||||||
lineparent = null;
|
lineparent = null;
|
||||||
}
|
}
|
||||||
@@ -63,14 +60,12 @@ public class ClangToken implements ClangNode {
|
|||||||
public ClangToken(ClangNode par, String txt) {
|
public ClangToken(ClangNode par, String txt) {
|
||||||
parent = par;
|
parent = par;
|
||||||
text = txt;
|
text = txt;
|
||||||
highlight = null;
|
|
||||||
syntax_type = DEFAULT_COLOR;
|
syntax_type = DEFAULT_COLOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClangToken(ClangNode par, String txt, int color) {
|
public ClangToken(ClangNode par, String txt, int color) {
|
||||||
parent = par;
|
parent = par;
|
||||||
text = txt;
|
text = txt;
|
||||||
highlight = null;
|
|
||||||
syntax_type = color;
|
syntax_type = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,19 +118,6 @@ public class ClangToken implements ClangNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setHighlight(Color val) {
|
|
||||||
highlight = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the background highlight color used to render this token, or null if not highlighted
|
|
||||||
* @return the Color or null
|
|
||||||
*/
|
|
||||||
public Color getHighlight() {
|
|
||||||
return highlight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set whether or not additional "matching" highlighting is applied to this token.
|
* Set whether or not additional "matching" highlighting is applied to this token.
|
||||||
* Currently this means a bounding box is drawn around the token.
|
* Currently this means a bounding box is drawn around the token.
|
||||||
|
|||||||
+1
-11
@@ -17,7 +17,6 @@ package ghidra.app.decompiler;
|
|||||||
|
|
||||||
import static ghidra.program.model.pcode.ElementId.*;
|
import static ghidra.program.model.pcode.ElementId.*;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -97,13 +96,6 @@ public class ClangTokenGroup implements ClangNode, Iterable<ClangNode> {
|
|||||||
return parent.getClangFunction();
|
return parent.getClangFunction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setHighlight(Color val) {
|
|
||||||
for (ClangNode element : tokgroup) {
|
|
||||||
element.setHighlight(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flatten(List<ClangNode> list) {
|
public void flatten(List<ClangNode> list) {
|
||||||
for (ClangNode element : tokgroup) {
|
for (ClangNode element : tokgroup) {
|
||||||
@@ -168,9 +160,7 @@ public class ClangTokenGroup implements ClangNode, Iterable<ClangNode> {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
String lastTokenStr = null;
|
String lastTokenStr = null;
|
||||||
StringBuffer buffer = new StringBuffer();
|
StringBuffer buffer = new StringBuffer();
|
||||||
Iterator<ClangNode> iter = tokgroup.iterator();
|
for (ClangNode node : tokgroup) {
|
||||||
while (iter.hasNext()) {
|
|
||||||
ClangNode node = iter.next();
|
|
||||||
String tokenStr = node.toString();
|
String tokenStr = node.toString();
|
||||||
if (tokenStr.length() == 0) {
|
if (tokenStr.length() == 0) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
+8
-5
@@ -27,9 +27,12 @@ import ghidra.app.decompiler.ClangToken;
|
|||||||
public class ClangFieldElement extends AbstractTextFieldElement {
|
public class ClangFieldElement extends AbstractTextFieldElement {
|
||||||
|
|
||||||
private final ClangToken token;
|
private final ClangToken token;
|
||||||
|
private ClangHighlightController hlController;
|
||||||
|
|
||||||
public ClangFieldElement(ClangToken token, AttributedString as, int col) {
|
public ClangFieldElement(ClangHighlightController hlController, ClangToken token,
|
||||||
|
AttributedString as, int col) {
|
||||||
super(as, 0, col);
|
super(as, 0, col);
|
||||||
|
this.hlController = hlController;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +42,7 @@ public class ClangFieldElement extends AbstractTextFieldElement {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paint(JComponent c, Graphics g, int x, int y) {
|
public void paint(JComponent c, Graphics g, int x, int y) {
|
||||||
Color highlightColor = token.getHighlight();
|
Color highlightColor = hlController.getCombinedColor(token);
|
||||||
if (highlightColor != null) {
|
if (highlightColor != null) {
|
||||||
g.setColor(highlightColor);
|
g.setColor(highlightColor);
|
||||||
g.fillRect(x, y - getHeightAbove(), getStringWidth(),
|
g.fillRect(x, y - getHeightAbove(), getStringWidth(),
|
||||||
@@ -63,12 +66,12 @@ public class ClangFieldElement extends AbstractTextFieldElement {
|
|||||||
if (as == attributedString) {
|
if (as == attributedString) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new ClangFieldElement(token, as, column + start);
|
return new ClangFieldElement(hlController, token, as, column + start);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FieldElement replaceAll(char[] targets, char replacement) {
|
public FieldElement replaceAll(char[] targets, char replacement) {
|
||||||
return new ClangFieldElement(token, attributedString.replaceAll(targets, replacement),
|
AttributedString as = attributedString.replaceAll(targets, replacement);
|
||||||
column);
|
return new ClangFieldElement(hlController, token, as, column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-31
@@ -30,6 +30,7 @@ import ghidra.program.model.pcode.HighFunction;
|
|||||||
import ghidra.program.model.pcode.PcodeOp;
|
import ghidra.program.model.pcode.PcodeOp;
|
||||||
import ghidra.util.ColorUtils;
|
import ghidra.util.ColorUtils;
|
||||||
import util.CollectionUtils;
|
import util.CollectionUtils;
|
||||||
|
import utility.function.Dummy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to handle highlights for a decompiled function.
|
* Class to handle highlights for a decompiled function.
|
||||||
@@ -86,7 +87,6 @@ public abstract class ClangHighlightController {
|
|||||||
|
|
||||||
// arbitrary value chosen by guessing; this can be changed if needed
|
// arbitrary value chosen by guessing; this can be changed if needed
|
||||||
private int maxColorBlendSize = 5;
|
private int maxColorBlendSize = 5;
|
||||||
private boolean isRebuilding;
|
|
||||||
|
|
||||||
private List<ClangHighlightListener> listeners = new ArrayList<>();
|
private List<ClangHighlightListener> listeners = new ArrayList<>();
|
||||||
|
|
||||||
@@ -169,33 +169,19 @@ public abstract class ClangHighlightController {
|
|||||||
// finished. This allows all highlights to calculate their matches without the color
|
// finished. This allows all highlights to calculate their matches without the color
|
||||||
// blending affecting performance.
|
// blending affecting performance.
|
||||||
//
|
//
|
||||||
isRebuilding = true;
|
|
||||||
Set<DecompilerHighlighter> service = getServiceHighlighters();
|
Set<DecompilerHighlighter> service = getServiceHighlighters();
|
||||||
Set<DecompilerHighlighter> secondary = getSecondaryHighlighters(function);
|
Set<DecompilerHighlighter> secondary = getSecondaryHighlighters(function);
|
||||||
Iterable<DecompilerHighlighter> it = CollectionUtils.asIterable(service, secondary);
|
Iterable<DecompilerHighlighter> it = CollectionUtils.asIterable(service, secondary);
|
||||||
|
|
||||||
try {
|
|
||||||
for (DecompilerHighlighter highlighter : it) {
|
for (DecompilerHighlighter highlighter : it) {
|
||||||
highlighter.clearHighlights();
|
highlighter.clearHighlights();
|
||||||
highlighter.applyHighlights();
|
highlighter.applyHighlights();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally {
|
|
||||||
isRebuilding = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// gather all highlighted tokens and then update their color
|
// gather all highlighted tokens and then update their color
|
||||||
Set<ClangToken> allTokens = new HashSet<>();
|
|
||||||
it = CollectionUtils.asIterable(service, secondary);
|
it = CollectionUtils.asIterable(service, secondary);
|
||||||
for (DecompilerHighlighter highlighter : it) {
|
for (DecompilerHighlighter highlighter : it) {
|
||||||
TokenHighlights hlTokens = userHighlights.add(highlighter);
|
userHighlights.add(highlighter);
|
||||||
for (HighlightToken hlToken : hlTokens) {
|
|
||||||
allTokens.add(hlToken.getToken());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ClangToken token : allTokens) {
|
|
||||||
updateHighlightColor(token);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +224,6 @@ public abstract class ClangHighlightController {
|
|||||||
public void clearPrimaryHighlights() {
|
public void clearPrimaryHighlights() {
|
||||||
Consumer<ClangToken> clearAll = token -> {
|
Consumer<ClangToken> clearAll = token -> {
|
||||||
token.setMatchingToken(false);
|
token.setMatchingToken(false);
|
||||||
updateHighlightColor(token);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
doClearHighlights(contextHighlightTokens, clearAll);
|
doClearHighlights(contextHighlightTokens, clearAll);
|
||||||
@@ -297,7 +282,7 @@ public abstract class ClangHighlightController {
|
|||||||
|
|
||||||
for (DecompilerHighlighter highlighter : highlighters) {
|
for (DecompilerHighlighter highlighter : highlighters) {
|
||||||
TokenHighlights highlights = userHighlights.getHighlights(highlighter);
|
TokenHighlights highlights = userHighlights.getHighlights(highlighter);
|
||||||
Consumer<ClangToken> clearHighlight = token -> updateHighlightColor(token);
|
Consumer<ClangToken> clearHighlight = Dummy.consumer();
|
||||||
doClearHighlights(highlights, clearHighlight);
|
doClearHighlights(highlights, clearHighlight);
|
||||||
}
|
}
|
||||||
highlighters.clear();
|
highlighters.clear();
|
||||||
@@ -333,7 +318,7 @@ public abstract class ClangHighlightController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Consumer<ClangToken> clearHighlight = token -> updateHighlightColor(token);
|
Consumer<ClangToken> clearHighlight = Dummy.consumer();
|
||||||
doClearHighlights(highlighterTokens, clearHighlight);
|
doClearHighlights(highlighterTokens, clearHighlight);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -417,18 +402,6 @@ public abstract class ClangHighlightController {
|
|||||||
|
|
||||||
// store the actual requested color
|
// store the actual requested color
|
||||||
currentHighlights.add(new HighlightToken(clangToken, highlightColor));
|
currentHighlights.add(new HighlightToken(clangToken, highlightColor));
|
||||||
updateHighlightColor(clangToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateHighlightColor(ClangToken t) {
|
|
||||||
|
|
||||||
if (isRebuilding) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the color to the current combined value of all highlight types
|
|
||||||
Color combinedColor = getCombinedColor(t);
|
|
||||||
t.setHighlight(combinedColor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add(Set<Color> colors, HighlightToken hlToken) {
|
private void add(Set<Color> colors, HighlightToken hlToken) {
|
||||||
|
|||||||
+3
-2
@@ -183,20 +183,21 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
|||||||
|
|
||||||
FieldElement[] elements = new FieldElement[tokens.size()];
|
FieldElement[] elements = new FieldElement[tokens.size()];
|
||||||
int columnPosition = 0;
|
int columnPosition = 0;
|
||||||
|
Program program = decompilerPanel.getProgram();
|
||||||
|
ClangHighlightController hlController = decompilerPanel.getHighlightController();
|
||||||
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 = getTokenColor(token);
|
Color color = getTokenColor(token);
|
||||||
|
|
||||||
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();
|
|
||||||
elements[i] =
|
elements[i] =
|
||||||
CommentUtils.parseTextForAnnotations(token.getText(), program, prototype, 0);
|
CommentUtils.parseTextForAnnotations(token.getText(), program, prototype, 0);
|
||||||
columnPosition += elements[i].length();
|
columnPosition += elements[i].length();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
AttributedString as = new AttributedString(token.getText(), color, metrics);
|
AttributedString as = new AttributedString(token.getText(), color, metrics);
|
||||||
elements[i] = new ClangFieldElement(token, as, columnPosition);
|
elements[i] = new ClangFieldElement(hlController, token, as, columnPosition);
|
||||||
columnPosition += as.length();
|
columnPosition += as.length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-9
@@ -2048,7 +2048,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
private void assertCombinedHighlightColor(ClangToken token) {
|
private void assertCombinedHighlightColor(ClangToken token) {
|
||||||
|
|
||||||
Color combinedColor = getCombinedHighlightColor(token);
|
Color combinedColor = getCombinedHighlightColor(token);
|
||||||
Color actual = token.getHighlight();
|
Color actual = getHighlight(provider, token);
|
||||||
assertEquals(combinedColor, actual);
|
assertEquals(combinedColor, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2139,7 +2139,8 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
DecompilerController controller = provider.getController();
|
DecompilerController controller = provider.getController();
|
||||||
DecompilerPanel panel = controller.getDecompilerPanel();
|
DecompilerPanel panel = controller.getDecompilerPanel();
|
||||||
Color hlColor = panel.getCurrentVariableHighlightColor();
|
Color hlColor = panel.getCurrentVariableHighlightColor();
|
||||||
assertEquals(hlColor, token.getHighlight());
|
Color actual = getHighlight(provider, token);
|
||||||
|
assertEquals(hlColor, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rename(String newName) {
|
private void rename(String newName) {
|
||||||
@@ -2265,7 +2266,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
// test the token under the cursor directly, as that may have a combined highlight applied
|
// test the token under the cursor directly, as that may have a combined highlight applied
|
||||||
Color combinedColor = getCombinedHighlightColor(theProvider, cursorToken);
|
Color combinedColor = getCombinedHighlightColor(theProvider, cursorToken);
|
||||||
ColorMatcher cm = new ColorMatcher(color, combinedColor);
|
ColorMatcher cm = new ColorMatcher(color, combinedColor);
|
||||||
Color actual = cursorToken.getHighlight();
|
Color actual = getHighlight(theProvider, cursorToken);
|
||||||
assertTrue("Token is not highlighted: '" + cursorToken + "'" + "\n\texpected: " + cm +
|
assertTrue("Token is not highlighted: '" + cursorToken + "'" + "\n\texpected: " + cm +
|
||||||
"; found: " + toString(actual), cm.matches(actual));
|
"; found: " + toString(actual), cm.matches(actual));
|
||||||
}
|
}
|
||||||
@@ -2283,7 +2284,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
// test the token under the cursor directly, as that may have a combined highlight applied
|
// test the token under the cursor directly, as that may have a combined highlight applied
|
||||||
Color combinedColor = getCombinedHighlightColor(token);
|
Color combinedColor = getCombinedHighlightColor(token);
|
||||||
ColorMatcher cm = new ColorMatcher(color, combinedColor);
|
ColorMatcher cm = new ColorMatcher(color, combinedColor);
|
||||||
Color actual = token.getHighlight();
|
Color actual = getHighlight(provider, token);
|
||||||
String tokenString = token.toString() + " at line " + token.getLineParent().getLineNumber();
|
String tokenString = token.toString() + " at line " + token.getLineParent().getLineNumber();
|
||||||
assertTrue("Token is not highlighted: '" + tokenString + "'" + "\n\texpected: " + cm +
|
assertTrue("Token is not highlighted: '" + tokenString + "'" + "\n\texpected: " + cm +
|
||||||
"; found: " + toString(actual), cm.matches(actual));
|
"; found: " + toString(actual), cm.matches(actual));
|
||||||
@@ -2330,7 +2331,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color actual = token.getHighlight();
|
Color actual = getHighlight(provider, token);
|
||||||
assertTrue("Token is not highlighted: '" + token + "'" + "\n\texpected: " + cm +
|
assertTrue("Token is not highlighted: '" + token + "'" + "\n\texpected: " + cm +
|
||||||
"; found: " + toString(actual), cm.matches(actual));
|
"; found: " + toString(actual), cm.matches(actual));
|
||||||
}
|
}
|
||||||
@@ -2357,7 +2358,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color actual = token.getHighlight();
|
Color actual = getHighlight(theProvider, token);
|
||||||
assertTrue("Token is not highlighted: '" + token + "'" + "\n\texpected: " + cm +
|
assertTrue("Token is not highlighted: '" + token + "'" + "\n\texpected: " + cm +
|
||||||
"; found: " + toString(actual), cm.matches(actual));
|
"; found: " + toString(actual), cm.matches(actual));
|
||||||
}
|
}
|
||||||
@@ -2378,7 +2379,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
assertAllFieldsHighlighted(theProvider, name, cm, ignores);
|
assertAllFieldsHighlighted(theProvider, name, cm, ignores);
|
||||||
|
|
||||||
// test the token under the cursor directly, as that may have a combined highlight applied
|
// test the token under the cursor directly, as that may have a combined highlight applied
|
||||||
Color actual = token.getHighlight();
|
Color actual = getHighlight(theProvider, token);
|
||||||
assertTrue("Token is not highlighted: '" + token + "'" + "\n\texpected: " + cm +
|
assertTrue("Token is not highlighted: '" + token + "'" + "\n\texpected: " + cm +
|
||||||
"; found: " + toString(actual), cm.matches(actual));
|
"; found: " + toString(actual), cm.matches(actual));
|
||||||
}
|
}
|
||||||
@@ -2394,7 +2395,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color actual = otherToken.getHighlight();
|
Color actual = getHighlight(theProvider, otherToken);
|
||||||
assertTrue("Token is not highlighted: '" + otherToken + "'" + "\n\texpected: " +
|
assertTrue("Token is not highlighted: '" + otherToken + "'" + "\n\texpected: " +
|
||||||
colorMatcher + "; found: " + toString(actual), colorMatcher.matches(actual));
|
colorMatcher + "; found: " + toString(actual), colorMatcher.matches(actual));
|
||||||
}
|
}
|
||||||
@@ -2411,7 +2412,7 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color actual = otherToken.getHighlight();
|
Color actual = getHighlight(theProvider, otherToken);
|
||||||
Color combinedColor = getCombinedHighlightColor(otherToken);
|
Color combinedColor = getCombinedHighlightColor(otherToken);
|
||||||
ColorMatcher combinedColorMatcher = colorMatcher.with(combinedColor);
|
ColorMatcher combinedColorMatcher = colorMatcher.with(combinedColor);
|
||||||
assertTrue(
|
assertTrue(
|
||||||
@@ -2421,6 +2422,12 @@ public class DecompilerClangTest extends AbstractDecompilerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Color getHighlight(DecompilerProvider theProvider, ClangToken token) {
|
||||||
|
DecompilerPanel panel = theProvider.getController().getDecompilerPanel();
|
||||||
|
ClangHighlightController highlightController = panel.getHighlightController();
|
||||||
|
return highlightController.getCombinedColor(token);
|
||||||
|
}
|
||||||
|
|
||||||
private String toString(Color c) {
|
private String toString(Color c) {
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
return "Color{null}";
|
return "Color{null}";
|
||||||
|
|||||||
Reference in New Issue
Block a user