diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DiffClangHighlightController.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DiffClangHighlightController.java index 8022db49b1..c02cd40f59 100755 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DiffClangHighlightController.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DiffClangHighlightController.java @@ -22,7 +22,6 @@ import java.util.stream.Collectors; import docking.widgets.EventTrigger; import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.support.FieldLocation; -import generic.theme.GColor; import ghidra.app.decompiler.ClangSyntaxToken; import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.component.*; @@ -120,7 +119,7 @@ public class DiffClangHighlightController extends LocationClangHighlightControll List tokens = addPrimaryHighlightToTokensForParenthesis( (ClangSyntaxToken) tok, defaultParenColor); reHighlightDiffs(tokens); - addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor); + addPrimaryHighlightToTokensForBrace((ClangSyntaxToken) tok, defaultParenColor); } TokenBin tokenBin = null; diff --git a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml index e131bffe1b..4b6b40b206 100644 --- a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml +++ b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml @@ -4370,7 +4370,8 @@ Middle-Click Highlights every occurrence of a variable, constant, or operator represented by the selected - token, within the Decompiler window. + token, within the Decompiler window. There are actions available from the popup menu and from + the keyboard to navigate to each highlighted token. @@ -4770,6 +4771,17 @@ + + Go To Next/Previous Highlight + + These actions are available from the popup menu and keyboard. Only tokens highlighted from the + middle-mouse will be navigated. Shift-Comma will go to the + previous highlighted token. Shift-Period will go to the + next highlighted token. These key bindings can be changed via the + Tool Options Dialog. + + + Highlight diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html index 98aa03a2a2..6fc00e3b8c 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html @@ -533,8 +533,10 @@

Highlights every occurrence of a variable, constant, or operator represented by the selected - token, within the Decompiler window. + token, within the Decompiler window. There are actions available from the popup menu and from + the keyboard to navigate to each highlighted token.

+ @@ -958,6 +960,19 @@

+
+

+Go To Next/Previous Highlight

+ +

+ These actions are available from the popup menu and keyboard. Only tokens highlighted from the + middle-mouse will be navigated. Shift-Comma will + go to the previous highlighted token. Shift-Period + will go to the next highlighted token. These key bindings can be changed via the + Tool Options Dialog. +

+
+

Highlight

diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangLine.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangLine.java index 7ca6f813bb..0ed643411d 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangLine.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/ClangLine.java @@ -25,7 +25,7 @@ import java.util.*; */ public class ClangLine { private int indent_level; - private ArrayList tokens; + private List tokens; private int lineNumber; public ClangLine(int lineNumber, int indent) { @@ -35,7 +35,7 @@ public class ClangLine { } public String getIndentString() { - StringBuffer buffer = new StringBuffer(); + StringBuilder buffer = new StringBuilder(); for (int i = 0; i < indent_level; i++) { buffer.append(PrettyPrinter.INDENT_STRING); } @@ -51,7 +51,7 @@ public class ClangLine { tok.setLineParent(this); } - public ArrayList getAllTokens() { + public List getAllTokens() { return tokens; } @@ -95,7 +95,6 @@ public class ClangLine { if (isCallout) { buffy.append(end); } - } return buffy.toString(); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/PrettyPrinter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/PrettyPrinter.java index 59e0435cf7..79c5977f6d 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/PrettyPrinter.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/PrettyPrinter.java @@ -37,7 +37,7 @@ public class PrettyPrinter { private Function function; private ClangTokenGroup tokgroup; - private ArrayList lines = new ArrayList<>(); + private List lines = new ArrayList<>(); private NameTransformer transformer; /** @@ -58,7 +58,7 @@ public class PrettyPrinter { private void padEmptyLines() { for (ClangLine line : lines) { - ArrayList tokenList = line.getAllTokens(); + List tokenList = line.getAllTokens(); if (tokenList.size() == 0) { ClangToken spacer = ClangToken.buildSpacer(null, line.getIndent(), INDENT_STRING); spacer.setLineParent(line); @@ -72,11 +72,11 @@ public class PrettyPrinter { } /** - * Returns an array list of the C language lines contained in the + * Returns a list of the C language lines contained in the * C language token group. - * @return an array list of the C language lines + * @return a list of the C language lines */ - public ArrayList getLines() { + public List getLines() { return lines; } @@ -86,8 +86,7 @@ public class PrettyPrinter { * @return a string of readable C code */ public DecompiledFunction print() { - StringBuffer buff = new StringBuffer(); - + StringBuilder buff = new StringBuilder(); for (ClangLine line : lines) { buff.append(line.getIndentString()); List tokens = line.getAllTokens(); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/TokenIterator.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/TokenIterator.java index aa4b1717cd..eded99ba6b 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/TokenIterator.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/TokenIterator.java @@ -15,8 +15,7 @@ */ package ghidra.app.decompiler; -import java.util.ArrayList; -import java.util.Iterator; +import java.util.*; /** * An iterator over ClangToken objects. The iterator walks a tree of ClangNode objects based on @@ -126,7 +125,7 @@ public class TokenIterator implements Iterator { * @param forward is true for a forward iterator, false for a backward iterator */ public TokenIterator(ClangToken token, boolean forward) { - ArrayList groupList = new ArrayList<>(); + List groupList = new ArrayList<>(); ClangNode node = token.Parent(); while (node != null) { groupList.add((ClangTokenGroup) node); @@ -154,7 +153,7 @@ public class TokenIterator implements Iterator { * @param forward is true for a forward iterator, false for a backward iterator */ public TokenIterator(ClangTokenGroup group, boolean forward) { - ArrayList groupList = new ArrayList<>(); + List groupList = new ArrayList<>(); ClangNode node = group; while (node instanceof ClangTokenGroup) { ClangTokenGroup curGroup = (ClangTokenGroup) node; diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java index c77bea769c..dae84a708c 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangDecompilerHighlighter.java @@ -19,6 +19,7 @@ import java.awt.Color; import java.util.*; import java.util.function.Supplier; +import generic.json.Json; import ghidra.app.decompiler.*; /** @@ -95,7 +96,7 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter { } Supplier> tokens = () -> highlights.keySet(); - ColorProvider colorProvider = t -> highlights.get(t); + ColorProvider colorProvider = new MappedTokenColorProvider(highlights); decompilerPanel.addHighlighterHighlights(this, tokens, colorProvider); clones.forEach(c -> c.applyHighlights()); @@ -155,6 +156,25 @@ class ClangDecompilerHighlighter implements DecompilerHighlighter { @Override public String toString() { - return super.toString() + ' ' + id; + return Json.toString(this, "matcher", "id"); + } + + private class MappedTokenColorProvider implements ColorProvider { + + private Map highlights; + + MappedTokenColorProvider(Map highlights) { + this.highlights = highlights; + } + + @Override + public Color getColor(ClangToken token) { + return highlights.get(token); + } + + @Override + public String toString() { + return "Token Matcher Color " + matcher.toString(); + } } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangHighlightController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangHighlightController.java index 29314cebaf..61d930e300 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangHighlightController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangHighlightController.java @@ -20,8 +20,6 @@ import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; -import org.apache.commons.collections4.map.LazyMap; - import docking.widgets.EventTrigger; import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.support.FieldLocation; @@ -42,7 +40,7 @@ import util.CollectionUtils; * *

This class maintains the following types of highlights: *

    - *
  • Primary Highlights - triggered by user clicking and some user actions; considered transient + *
  • Context Highlights - triggered by user clicking and some user actions; considered transient * and get cleared whenever the location changes. These highlights show state such as the * current field, impact of a variable (via a slicing action), or related syntax (such as * matching braces) @@ -77,21 +75,8 @@ public abstract class ClangHighlightController { protected Color defaultHighlightColor = DEFAULT_HIGHLIGHT_COLOR; protected Color defaultParenColor = DEFAULT_HIGHLIGHT_COLOR; - private TokenHighlights primaryHighlightTokens = new TokenHighlights(); - - private Map> secondaryHighlightersbyFunction = - LazyMap.lazyMap(new HashMap<>(), f -> new ArrayList<>()); - - // store the secondary highlighters here in addition to the map below so that we may discern - // between secondary highlights and highlight service highlights - private Set secondaryHighlighters = new HashSet<>(); - - // all highlighters, including secondary and highlight service highlighters - private Map highlighterHighlights = - new HashMap<>(); - - // color supplier for secondary highlights - private TokenHighlightColors secondaryHighlightColors = new TokenHighlightColors(); + private TokenHighlights contextHighlightTokens = new TokenHighlights(); + private UserHighlights userHighlights = new UserHighlights(); /** * A counter to track updates so that clients know when a buffered highlight request is invalid @@ -113,21 +98,8 @@ public abstract class ClangHighlightController { * color. * @return the color provider */ - public ColorProvider getRandomColorProvider() { - return token -> secondaryHighlightColors.getColor(token.getText()); - } - - /** - * Returns the token that has the primary highlight applied, if any. If multiple tokens are - * highlighted, then the return value is arbitrary. - * @return the highlighted text - */ - public String getPrimaryHighlightedText() { - ClangToken highlightedToken = getHighlightedToken(); - if (highlightedToken != null) { - return highlightedToken.getText(); - } - return null; + public ColorProvider getGeneratedColorProvider() { + return new GeneratedColorProvider(); } /** @@ -139,8 +111,8 @@ public abstract class ClangHighlightController { return updateId; } - public boolean hasPrimaryHighlight(ClangToken token) { - return primaryHighlightTokens.contains(token); + public boolean hasContextHighlight(ClangToken token) { + return contextHighlightTokens.contains(token); } public boolean hasSecondaryHighlight(ClangToken token) { @@ -148,26 +120,19 @@ public abstract class ClangHighlightController { } public boolean hasSecondaryHighlights(Function function) { - return !secondaryHighlightersbyFunction.get(function).isEmpty(); + return userHighlights.hasSecondaryHighlights(function); } public Color getSecondaryHighlight(ClangToken token) { - DecompilerHighlighter highlighter = getSecondaryHighlighter(token); - if (highlighter != null) { - TokenHighlights highlights = highlighterHighlights.get(highlighter); - HighlightToken hlToken = highlights.get(token); - return hlToken.getColor(); - } - - return null; + return userHighlights.getSecondaryHighlight(token); } public TokenHighlightColors getSecondaryHighlightColors() { - return secondaryHighlightColors; + return userHighlights.getSecondaryHighlightColors(); } public TokenHighlights getPrimaryHighlights() { - return primaryHighlightTokens; + return contextHighlightTokens; } /** @@ -177,8 +142,8 @@ public abstract class ClangHighlightController { * @param function the function * @return the highlighters */ - public Set getSecondaryHighlighters(Function function) { - return new HashSet<>(secondaryHighlightersbyFunction.get(function)); + public Set getSecondaryHighlighters(Function function) { + return userHighlights.getSecondaryHighlighters(function); } /** @@ -187,11 +152,8 @@ public abstract class ClangHighlightController { * function-specific. * @return the highlighters */ - public Set getGlobalHighlighters() { - Set allHighlighters = highlighterHighlights.keySet(); - Set results = new HashSet<>(allHighlighters); - results.removeAll(secondaryHighlighters); - return results; + public Set getGlobalHighlighters() { + return userHighlights.getGlobalHighlighters(); } /** @@ -201,7 +163,7 @@ public abstract class ClangHighlightController { * @see #getPrimaryHighlights() */ public TokenHighlights getHighlighterHighlights(DecompilerHighlighter highlighter) { - return highlighterHighlights.get(highlighter); + return userHighlights.getHighlights(highlighter); } /** @@ -209,8 +171,8 @@ public abstract class ClangHighlightController { * @return token or null */ public ClangToken getHighlightedToken() { - if (primaryHighlightTokens.size() == 1) { - HighlightToken hlToken = CollectionUtils.any(primaryHighlightTokens); + if (contextHighlightTokens.size() == 1) { + HighlightToken hlToken = CollectionUtils.any(contextHighlightTokens); return hlToken.getToken(); } return null; @@ -236,7 +198,7 @@ public abstract class ClangHighlightController { updateHighlightColor(token); }; - doClearHighlights(primaryHighlightTokens, clearAll); + doClearHighlights(contextHighlightTokens, clearAll); notifyListeners(); } @@ -262,10 +224,9 @@ public abstract class ClangHighlightController { * @param tokens the tokens */ public void togglePrimaryHighlights(Color hlColor, Supplier> tokens) { - boolean isAllHighlighted = true; - for (ClangToken otherToken : tokens.get()) { - if (!hasPrimaryHighlight(otherToken)) { + for (ClangToken token : tokens.get()) { + if (!hasContextHighlight(token)) { isAllHighlighted = false; break; } @@ -276,8 +237,7 @@ public abstract class ClangHighlightController { clearPrimaryHighlights(); if (isAllHighlighted) { - // nothing to do; we toggled from 'all on' to 'all off' - return; + return; // nothing to do; we toggled from 'all on' to 'all off' } addPrimaryHighlights(tokens, hlColor); @@ -289,9 +249,11 @@ public abstract class ClangHighlightController { */ public void removeSecondaryHighlights(Function f) { - List highlighters = secondaryHighlightersbyFunction.get(f); - for (ClangDecompilerHighlighter highlighter : highlighters) { - TokenHighlights highlights = highlighterHighlights.get(highlighter); + List highlighters = + userHighlights.getSecondaryHighlightersByFunction(f); + + for (DecompilerHighlighter highlighter : highlighters) { + TokenHighlights highlights = userHighlights.getHighlights(highlighter); Consumer clearHighlight = token -> updateHighlightColor(token); doClearHighlights(highlights, clearHighlight); } @@ -305,38 +267,16 @@ public abstract class ClangHighlightController { * @see #removeSecondaryHighlights(Function) */ public void removeSecondaryHighlights(ClangToken token) { - DecompilerHighlighter highlighter = getSecondaryHighlighter(token); + DecompilerHighlighter highlighter = userHighlights.getSecondaryHighlighter(token); if (highlighter != null) { highlighter.dispose(); // this will call removeHighlighterHighlights() } notifyListeners(); } - private DecompilerHighlighter getSecondaryHighlighter(ClangToken token) { - for (DecompilerHighlighter highlighter : secondaryHighlighters) { - TokenHighlights highlights = highlighterHighlights.get(highlighter); - HighlightToken hlToken = highlights.get(token); - if (hlToken != null) { - return highlighter; - } - } - - return null; - } - public void removeHighlighter(DecompilerHighlighter highlighter) { - removeHighlighterHighlights(highlighter); - highlighterHighlights.remove(highlighter); - secondaryHighlighters.remove(highlighter); - - Collection> lists = - secondaryHighlightersbyFunction.values(); - for (List highlighters : lists) { - if (highlighters.remove(highlighter)) { - break; - } - } + userHighlights.remove(highlighter); } /** @@ -345,7 +285,7 @@ public abstract class ClangHighlightController { */ public void removeHighlighterHighlights(DecompilerHighlighter highlighter) { - TokenHighlights highlighterTokens = highlighterHighlights.get(highlighter); + TokenHighlights highlighterTokens = userHighlights.get(highlighter); if (highlighterTokens == null) { return; } @@ -361,59 +301,56 @@ public abstract class ClangHighlightController { * @param function the function * @param highlighter the highlighter */ - public void addSecondaryHighlighter(Function function, ClangDecompilerHighlighter highlighter) { - - // Note: this highlighter has likely already been added the the this class, but has not - // yet been bound to the given function. - secondaryHighlightersbyFunction.get(function).add(highlighter); - secondaryHighlighters.add(highlighter); - highlighterHighlights.putIfAbsent(highlighter, new TokenHighlights()); + public void addSecondaryHighlighter(Function function, DecompilerHighlighter highlighter) { + userHighlights.addSecondaryHighlighter(function, highlighter); } // Note: this is used for all highlight types, secondary and highlighter service highlighters public void addHighlighter(ClangDecompilerHighlighter highlighter) { - highlighterHighlights.putIfAbsent(highlighter, new TokenHighlights()); + userHighlights.add(highlighter); } // Note: this is used for all highlight types, secondary and highlighter service highlights - public void addHighlighterHighlights(ClangDecompilerHighlighter highlighter, - Supplier> tokens, - ColorProvider colorProvider) { + public void addHighlighterHighlights(DecompilerHighlighter highlighter, + Supplier> tokens, ColorProvider colorProvider) { Objects.requireNonNull(highlighter); - TokenHighlights highlighterTokens = - highlighterHighlights.computeIfAbsent(highlighter, k -> new TokenHighlights()); + TokenHighlights highlighterTokens = userHighlights.add(highlighter); addTokensToHighlights(tokens.get(), colorProvider, highlighterTokens); } private void addPrimaryHighlights(Supplier> tokens, Color hlColor) { - ColorProvider colorProvider = token -> hlColor; - addTokensToHighlights(tokens.get(), colorProvider, primaryHighlightTokens); + addPrimaryHighlights(tokens.get(), hlColor); + } + + private void addPrimaryHighlights(Collection tokens, Color hlColor) { + ColorProvider colorProvider = new DefaultColorProvider("Tokens Highlight Color", hlColor); + addTokensToHighlights(tokens, colorProvider, contextHighlightTokens); } public void addPrimaryHighlights(ClangNode parentNode, Set ops, Color hlColor) { - addPrimaryHighlights(parentNode, token -> { - PcodeOp op = token.getPcodeOp(); - return ops.contains(op) ? hlColor : null; - }); + ColorProvider colorProvider = new DefaultColorProvider("PcodeOp Highlight Color", hlColor) { + @Override + public Color getColor(ClangToken token) { + PcodeOp op = token.getPcodeOp(); + return ops.contains(op) ? hlColor : null; + } + }; + + addPrimaryHighlights(parentNode, colorProvider); } public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) { Set tokens = new HashSet<>(); gatherAllTokens(parentNode, tokens); - addTokensToHighlights(tokens, colorProvider::getColor, primaryHighlightTokens); + addTokensToHighlights(tokens, colorProvider, contextHighlightTokens); } - private void addPrimaryHighlights(Collection tokens, Color hlColor) { - ColorProvider colorProvider = token -> hlColor; - addTokensToHighlights(tokens, colorProvider, primaryHighlightTokens); - } - - private void addTokensToHighlights(Collection tokens, - ColorProvider colorProvider, TokenHighlights currentHighlights) { + private void addTokensToHighlights(Collection tokens, ColorProvider colorProvider, + TokenHighlights currentHighlights) { updateId++; @@ -441,7 +378,7 @@ public abstract class ClangHighlightController { } private void updateHighlightColor(ClangToken t) { - // set the color to the current combined value of both highlight types + // set the color to the current combined value of all highlight types Color combinedColor = getCombinedColor(t); t.setHighlight(combinedColor); } @@ -469,7 +406,7 @@ public abstract class ClangHighlightController { // note: not sure whether we should always blend all colors or decide to allow some // highlighters have precedence for highlighting - HighlightToken primaryHl = primaryHighlightTokens.get(t); + HighlightToken primaryHl = contextHighlightTokens.get(t); Color blendedHlColor = blendHighlighterColors(t); List allColors = new ArrayList<>(); @@ -506,12 +443,12 @@ public abstract class ClangHighlightController { return null; // not sure if this can happen } - Set global = getGlobalHighlighters(); - Set secondary = getSecondaryHighlighters(function); - Iterable it = CollectionUtils.asIterable(global, secondary); + Set global = getGlobalHighlighters(); + Set secondary = getSecondaryHighlighters(function); + Iterable it = CollectionUtils.asIterable(global, secondary); Color lastColor = null; - for (ClangDecompilerHighlighter highlighter : it) { - TokenHighlights highlights = highlighterHighlights.get(highlighter); + for (DecompilerHighlighter highlighter : it) { + TokenHighlights highlights = userHighlights.get(highlighter); HighlightToken hlToken = highlights.get(token); if (hlToken == null) { continue; @@ -542,6 +479,24 @@ public abstract class ClangHighlightController { return highFunction.getFunction(); } + protected void addPrimaryHighlightToTokensForBrace(ClangSyntaxToken token, + Color highlightColor) { + + if (DecompilerUtils.isBrace(token)) { + highlightBrace(token, highlightColor); + notifyListeners(); + } + } + + private void highlightBrace(ClangSyntaxToken startToken, Color highlightColor) { + + ClangSyntaxToken matchingBrace = DecompilerUtils.getMatchingBrace(startToken); + if (matchingBrace != null) { + matchingBrace.setMatchingToken(true); // this is a signal to the painter + addPrimaryHighlights(Set.of(matchingBrace), highlightColor); + } + } + /** * If input token is a parenthesis, highlight all tokens between it and its match * @param tok potential parenthesis token @@ -609,23 +564,6 @@ public abstract class ClangHighlightController { return results; } - public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) { - - if (DecompilerUtils.isBrace(token)) { - highlightBrace(token, highlightColor); - notifyListeners(); - } - } - - private void highlightBrace(ClangSyntaxToken startToken, Color highlightColor) { - - ClangSyntaxToken matchingBrace = DecompilerUtils.getMatchingBrace(startToken); - if (matchingBrace != null) { - matchingBrace.setMatchingToken(true); // this is a signal to the painter - addPrimaryHighlights(Set.of(matchingBrace), highlightColor); - } - } - public void addListener(ClangHighlightListener listener) { listeners.add(listener); } @@ -642,9 +580,21 @@ public abstract class ClangHighlightController { public void dispose() { listeners.clear(); - primaryHighlightTokens.clear(); - secondaryHighlighters.clear(); - secondaryHighlightersbyFunction.clear(); - highlighterHighlights.clear(); + contextHighlightTokens.clear(); + userHighlights.dispose(); } + + private class GeneratedColorProvider implements ColorProvider { + + @Override + public Color getColor(ClangToken token) { + return userHighlights.getSecondaryColor(token.getText()); + } + + @Override + public String toString() { + return "Generated Color Provider " + userHighlights.getAppliedColorsString(); + } + } + } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java index 03065bcbd7..4bb3d8c4cc 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java @@ -39,8 +39,6 @@ import ghidra.program.model.pcode.HighFunction; import ghidra.util.Msg; /** - * - * * Control the GUI layout for displaying tokenized C code */ public class ClangLayoutController implements LayoutModel, LayoutModelListener { @@ -53,10 +51,10 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { private Field[] fieldList; // Array of fields comprising layout private FontMetrics metrics; private FieldHighlightFactory hlFactory; - private ArrayList listeners; + private List listeners; private Color[] syntaxColor; // Foreground colors. private BigInteger numIndexes = BigInteger.ZERO; - private ArrayList lines = new ArrayList<>(); + private List lines = new ArrayList<>(); private boolean showLineNumbers = true; @@ -71,7 +69,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { buildLayouts(null, null, null, false); } - public ArrayList getLines() { + public List getLines() { return lines; } @@ -248,7 +246,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { } } - private void splitToMaxWidthLines(ArrayList res, String line) { + private void splitToMaxWidthLines(List res, String line) { int maxchar; if ((maxWidth == 0) || (indentWidth == 0)) { maxchar = 40; @@ -257,7 +255,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { maxchar = maxWidth / indentWidth; } String[] toklist = line.split("[ \t]+"); - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); int cursize = 0; boolean atleastone = false; int i = 0; @@ -275,7 +273,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { res.add(finishLine); cursize = 5; atleastone = false; - buf = new StringBuffer(); + buf = new StringBuilder(); buf.append(" "); } else { @@ -302,7 +300,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { return false; // No error message to add } String[] errlines_init = errmsg.split("[\n\r]+"); - ArrayList errlines = new ArrayList<>(); + List errlines = new ArrayList<>(); for (String element : errlines_init) { splitToMaxWidthLines(errlines, element); } @@ -345,16 +343,16 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { java.util.function.Function matcher, String searchString, FieldLocation currentLocation) { - int row = currentLocation.getIndex().intValue(); - for (int i = row; i < fieldList.length; i++) { - ClangTextField field = (ClangTextField) fieldList[i]; - String partialLine = - getTextLineFromOffset((i == row) ? currentLocation : null, field, true); - SearchMatch match = matcher.apply(partialLine); + int startRow = currentLocation.getIndex().intValue(); + for (int row = startRow; row < fieldList.length; row++) { + ClangTextField field = (ClangTextField) fieldList[row]; + FieldLocation location = (row == startRow) ? currentLocation : null; + String lineText = getLineTextFromOffset(location, field, true); + SearchMatch match = matcher.apply(lineText); if (match == SearchMatch.NO_MATCH) { continue; } - if (i == row) { // cursor is on this line + if (row == startRow) { // cursor is on this line // // The match start for all lines without the cursor will be relative to the start // of the line, which is 0. However, when searching on the row with the cursor, @@ -362,13 +360,15 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { // compensate for the difference between the start of the line and the cursor. // String fullLine = field.getText(); - int cursorOffset = fullLine.length() - partialLine.length(); + int cursorOffset = fullLine.length() - lineText.length(); match.start += cursorOffset; match.end += cursorOffset; } - FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field); - FieldLocation fieldLocation = - new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn()); + + // we use 0 here because currently there is only one field, which is the entire line + int fieldNum = 0; + int column = getScreenColumnFromOffset(match.start, field); + FieldLocation fieldLocation = new FieldLocation(row, fieldNum, 0, column); return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1, searchString, true); @@ -380,17 +380,19 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { java.util.function.Function matcher, String searchString, FieldLocation currentLocation) { - int row = currentLocation.getIndex().intValue(); - for (int i = row; i >= 0; i--) { - ClangTextField field = (ClangTextField) fieldList[i]; - String textLine = - getTextLineFromOffset((i == row) ? currentLocation : null, field, false); + int startRow = currentLocation.getIndex().intValue(); + for (int row = startRow; row >= 0; row--) { + ClangTextField field = (ClangTextField) fieldList[row]; + FieldLocation location = (row == startRow) ? currentLocation : null; + String lineText = getLineTextFromOffset(location, field, false); - SearchMatch match = matcher.apply(textLine); + SearchMatch match = matcher.apply(lineText); if (match != SearchMatch.NO_MATCH) { - FieldNumberColumnPair pair = getFieldIndexFromOffset(match.start, field); - FieldLocation fieldLocation = - new FieldLocation(i, pair.getFieldNumber(), 0, pair.getColumn()); + + // we use 0 here because currently there is only one field, which is the entire line + int fieldNum = 0; + int column = getScreenColumnFromOffset(match.start, field); + FieldLocation fieldLocation = new FieldLocation(row, fieldNum, 0, column); return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1, searchString, false); @@ -483,7 +485,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { return findNextTokenGoingBackward(function, searchString, currentLocation); } - private String getTextLineFromOffset(FieldLocation location, ClangTextField textField, + private String getLineTextFromOffset(FieldLocation location, ClangTextField textField, boolean forwardSearch) { if (location == null) { // the cursor location is not on this line; use all of the text @@ -494,32 +496,28 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { return ""; } - String partialText = textField.getText(); - + String lineText = textField.getText(); if (forwardSearch) { int nextCol = location.getCol(); // protects against the location column being out of range (this can happen if we're // searching forward and the cursor is past the last token) - if (nextCol >= partialText.length()) { + if (nextCol >= lineText.length()) { return ""; } // skip a character to start the next search; this prevents matching the previous match - return partialText.substring(nextCol); + return lineText.substring(nextCol); } // backwards search - return partialText.substring(0, location.getCol()); + return lineText.substring(0, location.getCol()); } - private FieldNumberColumnPair getFieldIndexFromOffset(int screenOffset, - ClangTextField textField) { - RowColLocation rowColLocation = textField.textOffsetToScreenLocation(screenOffset); - - // we use 0 here because currently there is only one field, which is the entire line - return new FieldNumberColumnPair(0, rowColLocation.col()); + private int getScreenColumnFromOffset(int textOffset, ClangTextField textField) { + RowColLocation rowColLocation = textField.textOffsetToScreenLocation(textOffset); + return rowColLocation.col(); } private static class SearchMatch { @@ -565,27 +563,4 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { public void flushChanges() { // nothing to do } -//================================================================================================== -// Inner Classes -//================================================================================================== - - private class FieldNumberColumnPair { - private final int fieldNumber; - private final int column; - - FieldNumberColumnPair(int fieldNumber, int column) { - this.fieldNumber = fieldNumber; - this.column = column; - - } - - int getFieldNumber() { - return fieldNumber; - } - - int getColumn() { - return column; - } - } - } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangTextField.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangTextField.java index 36ccb9e80d..c492efa559 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangTextField.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangTextField.java @@ -18,8 +18,8 @@ package ghidra.app.decompiler.component; import java.util.List; import docking.widgets.fieldpanel.field.*; -import docking.widgets.fieldpanel.support.FieldLocation; import docking.widgets.fieldpanel.support.FieldHighlightFactory; +import docking.widgets.fieldpanel.support.FieldLocation; import ghidra.app.decompiler.ClangToken; public class ClangTextField extends WrappingVerticalLayoutTextField { @@ -123,4 +123,12 @@ public class ClangTextField extends WrappingVerticalLayoutTextField { public int getLineNumber() { return lineNumber; } + + public ClangToken getFirstToken() { + return tokenList.get(0); + } + + public ClangToken getLastToken() { + return tokenList.get(tokenList.size() - 1); + } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java index dc1e215efa..d9a64fa6c9 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java @@ -80,7 +80,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field private FieldHighlightFactory hlFactory; private ClangHighlightController highlightController; - private Map highlightersById = new HashMap<>(); + private Map highlightersById = new HashMap<>(); private PendingHighlightUpdate pendingHighlightUpdate; private SwingUpdateManager highlighCursorUpdater = new SwingUpdateManager(() -> { if (pendingHighlightUpdate != null) { @@ -89,6 +89,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } }); + private ActiveMiddleMouse activeMiddleMouse; private int middleMouseHighlightButton; private Color middleMouseHighlightColor; private Color currentVariableHighlightColor; @@ -194,7 +195,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return highlightController.getHighlighterHighlights(highligter); } - private Set getSecondaryHighlihgtersByFunction(Function function) { + public TokenHighlights getMiddleMouseHighlights() { + if (activeMiddleMouse != null) { + return activeMiddleMouse.getHighlights(); + } + return null; + } + + private Set getSecondaryHighlihgtersByFunction(Function function) { return highlightController.getSecondaryHighlighters(function); } @@ -212,31 +220,50 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } public void addSecondaryHighlight(ClangToken token) { - ColorProvider cp = highlightController.getRandomColorProvider(); + ColorProvider cp = highlightController.getGeneratedColorProvider(); addSecondaryHighlight(token.getText(), cp); } public void addSecondaryHighlight(ClangToken token, Color color) { - ColorProvider cp = t -> color; + ColorProvider cp = new DefaultColorProvider("User Secondary Highlight", color); addSecondaryHighlight(token.getText(), cp); } private void addSecondaryHighlight(String tokenText, ColorProvider colorProvider) { NameTokenMatcher matcher = new NameTokenMatcher(tokenText, colorProvider); - ClangDecompilerHighlighter highlighter = createHighlighter(matcher); + DecompilerHighlighter highlighter = createHighlighter(matcher); applySecondaryHighlights(highlighter); } - private void applySecondaryHighlights(ClangDecompilerHighlighter highlighter) { + private void applySecondaryHighlights(DecompilerHighlighter highlighter) { Function function = decompileData.getFunction(); highlightController.addSecondaryHighlighter(function, highlighter); highlighter.applyHighlights(); } - private void togglePrimaryHighlight(FieldLocation location, Field field, Color highlightColor) { + private void toggleMiddleMouseHighlight(FieldLocation location, Field field) { ClangToken token = ((ClangTextField) field).getToken(location); - Supplier> lazyTokens = () -> findTokensByName(token.getText()); - highlightController.togglePrimaryHighlights(middleMouseHighlightColor, lazyTokens); + ColorProvider cp = new MiddleMouseColorProvider(); + NameTokenMatcher matcher = new NameTokenMatcher(token.getText(), cp); + + ActiveMiddleMouse previousMiddleMouse = activeMiddleMouse; + activeMiddleMouse = null; + + if (previousMiddleMouse != null) { + + // middle mousing always clears the last middle-mouse highlight + previousMiddleMouse.clear(); + + if (previousMiddleMouse.matches(token)) { + // middle mousing on the same token clears, but does not create a new highlight + return; + } + } + + DecompilerHighlighter newMiddleMouseHighlighter = createHighlighter(matcher); + ActiveMiddleMouse newMiddleMouse = new ActiveMiddleMouse(token, newMiddleMouseHighlighter); + newMiddleMouse.apply(); + activeMiddleMouse = newMiddleMouse; } void addHighlighterHighlights(ClangDecompilerHighlighter highlighter, @@ -248,14 +275,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field highlightController.removeHighlighterHighlights(highlighter); } - public ClangDecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm) { + public DecompilerHighlighter createHighlighter(CTokenHighlightMatcher tm) { UUID uuId = UUID.randomUUID(); String id = uuId.toString(); return createHighlighter(id, tm); } - public ClangDecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm) { - ClangDecompilerHighlighter currentHighlighter = highlightersById.get(id); + public DecompilerHighlighter createHighlighter(String id, CTokenHighlightMatcher tm) { + DecompilerHighlighter currentHighlighter = highlightersById.get(id); if (currentHighlighter != null) { currentHighlighter.dispose(); } @@ -271,7 +298,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } void removeHighlighter(String id) { - ClangDecompilerHighlighter highlighter = highlightersById.remove(id); + DecompilerHighlighter highlighter = highlightersById.remove(id); highlightController.removeHighlighter(highlighter); } @@ -339,11 +366,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field private void cloneGlobalHighlighters(DecompilerPanel sourcePanel) { - Set globalHighlighters = + Set globalHighlighters = sourcePanel.highlightController.getGlobalHighlighters(); - for (ClangDecompilerHighlighter otherHighlighter : globalHighlighters) { + for (DecompilerHighlighter otherHighlighter : globalHighlighters) { - ClangDecompilerHighlighter newHighlighter = otherHighlighter.clone(this); + if (!(otherHighlighter instanceof ClangDecompilerHighlighter clangHighlighter)) { + continue; + } + + DecompilerHighlighter newHighlighter = clangHighlighter.clone(this); highlightersById.put(newHighlighter.getId(), newHighlighter); TokenHighlights otherHighlighterTokens = @@ -374,15 +405,20 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field // clone will match the cloned decompiler. // Function function = decompileData.getFunction(); - Set secondaryHighlighters = + Set secondaryHighlighters = sourcePanel.getSecondaryHighlihgtersByFunction(function); // // We do NOT clone the secondary highlighters. This allows the user the remove them // from the primary provider without effecting the cloned provider and vice versa. // - for (ClangDecompilerHighlighter highlighter : secondaryHighlighters) { - ClangDecompilerHighlighter newHighlighter = highlighter.copy(this); + for (DecompilerHighlighter highlighter : secondaryHighlighters) { + + if (!(highlighter instanceof ClangDecompilerHighlighter clangHighlighter)) { + continue; + } + + DecompilerHighlighter newHighlighter = clangHighlighter.copy(this); highlightersById.put(newHighlighter.getId(), newHighlighter); applySecondaryHighlights(newHighlighter); } @@ -455,9 +491,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return; } - Set globalHighlighters = - highlightController.getGlobalHighlighters(); - for (ClangDecompilerHighlighter highlighter : globalHighlighters) { + Set globalHighlighters = highlightController.getGlobalHighlighters(); + for (DecompilerHighlighter highlighter : globalHighlighters) { highlighter.clearHighlights(); highlighter.applyHighlights(); } @@ -470,9 +505,9 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return; } - Set secondaryHighlighters = + Set secondaryHighlighters = getSecondaryHighlihgtersByFunction(function); - for (ClangDecompilerHighlighter highlighter : secondaryHighlighters) { + for (DecompilerHighlighter highlighter : secondaryHighlighters) { highlighter.clearHighlights(); highlighter.applyHighlights(); } @@ -719,7 +754,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } if (buttonState == middleMouseHighlightButton && clickCount == 1) { - togglePrimaryHighlight(location, field, middleMouseHighlightColor); + toggleMiddleMouseHighlight(location, field); } } @@ -770,19 +805,6 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field controller.goToFunction(function, newWindow); return; } - - // TODO no idea what this is supposed to be handling...someone doc this please -// String labelName = functionToken.getText(); -// if (labelName.startsWith("func_0x")) { -// try { -// Address addr = -// decompileData.getFunction().getEntryPoint().getAddress(labelName.substring(7)); -// controller.goToAddress(addr, newWindow); -// } -// catch (AddressFormatException e) { -// controller.goToLabel(labelName, newWindow); -// } -// } } private void tryGoToLabel(ClangLabelToken token, boolean newWindow) { @@ -1377,4 +1399,57 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } } } + + private class MiddleMouseColorProvider implements ColorProvider { + + @Override + public Color getColor(ClangToken token) { + return middleMouseHighlightColor; + } + + @Override + public String toString() { + return "Middle Mouse Color Provider " + middleMouseHighlightColor; + } + } + + /** + * A class to track the current middle moused token. + */ + private class ActiveMiddleMouse { + + private ClangToken token; + private DecompilerHighlighter highlighter; + + ActiveMiddleMouse(ClangToken token, DecompilerHighlighter highlighter) { + this.token = token; + this.highlighter = highlighter; + } + + TokenHighlights getHighlights() { + return highlightController.getHighlighterHighlights(highlighter); + } + + DecompilerHighlighter getHighlighter() { + return highlighter; + } + + boolean matches(ClangToken other) { + return token.getText().equals(other.getText()); + } + + void clear() { + highlightController.removeHighlighter(highlighter); + } + + void apply() { + applySecondaryHighlights(highlighter); + } + + @Override + public String toString() { + return "Middle Mouse Token " + token; + } + } + } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java index e9416ac6f5..1755300c87 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java @@ -33,9 +33,10 @@ import ghidra.program.model.pcode.*; public class DecompilerUtils { /** - * Gaither decompiler options from tool and program. If tool is null or does not provide + * Gather decompiler options from tool and program. If tool is null or does not provide * a {@link OptionsService} provider only options stored within the program will be consumed. - * @param serviceProvider plugin tool or service provider providing access to {@link OptionsService} + * @param serviceProvider plugin tool or service provider providing access to + * {@link OptionsService} * @param program program * @return decompiler options */ @@ -281,7 +282,7 @@ public class DecompilerUtils { * @param function decompiled function * @return true if {@code var} corresponds to existing auto {@code this} parameter, else false */ - public static boolean testForAutoParameterThis(HighVariable var, Function function) { + public static boolean isThisParameter(HighVariable var, Function function) { if (var instanceof HighParam) { int slot = ((HighParam) var).getSlot(); Parameter parameter = function.getParameter(slot); @@ -626,7 +627,7 @@ public class DecompilerUtils { String destinationStart = label.getText() + ':'; Address address = label.getMinAddress(); - List tokens = DecompilerUtils.getTokens(root, address); + List tokens = getTokens(root, address); for (ClangToken token : tokens) { if (isGoToStatement(token)) { continue; // ignore any goto statements @@ -791,7 +792,7 @@ public class DecompilerUtils { * @param group is the token hierarchy * @return the array of ClangLine objects */ - public static ArrayList toLines(ClangTokenGroup group) { + public static List toLines(ClangTokenGroup group) { List alltoks = new ArrayList<>(); group.flatten(alltoks); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DefaultColorProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DefaultColorProvider.java new file mode 100644 index 0000000000..a8cecdad87 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DefaultColorProvider.java @@ -0,0 +1,49 @@ +/* ### + * 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; + +import java.awt.Color; + +import ghidra.app.decompiler.ClangToken; + +/** + * A color provider that returns a specific color. + */ +public class DefaultColorProvider implements ColorProvider { + + private Color color; + private String prefix; + + /** + * Constructor + * @param prefix a descriptive prefix used in the {@link #toString()} method + * @param color the color + */ + DefaultColorProvider(String prefix, Color color) { + this.prefix = prefix; + this.color = color; + } + + @Override + public Color getColor(ClangToken token) { + return color; + } + + @Override + public String toString() { + return prefix + ' ' + color; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/LocationClangHighlightController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/LocationClangHighlightController.java index 761e586c86..4d291b8d10 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/LocationClangHighlightController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/LocationClangHighlightController.java @@ -50,7 +50,7 @@ public class LocationClangHighlightController extends ClangHighlightController { addPrimaryHighlight(tok, defaultHighlightColor); if (tok instanceof ClangSyntaxToken) { addPrimaryHighlightToTokensForParenthesis((ClangSyntaxToken) tok, defaultParenColor); - addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor); + addPrimaryHighlightToTokensForBrace((ClangSyntaxToken) tok, defaultParenColor); } } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NameTokenMatcher.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NameTokenMatcher.java index 934d3a1391..029f16e0cb 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NameTokenMatcher.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NameTokenMatcher.java @@ -17,6 +17,7 @@ package ghidra.app.decompiler.component; import java.awt.Color; +import generic.json.Json; import ghidra.app.decompiler.CTokenHighlightMatcher; import ghidra.app.decompiler.ClangToken; @@ -40,4 +41,9 @@ class NameTokenMatcher implements CTokenHighlightMatcher { } return null; } + + @Override + public String toString() { + return Json.toString(this); + } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NullClangHighlightController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NullClangHighlightController.java index 2a0b14677c..9fb8120e3f 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NullClangHighlightController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/NullClangHighlightController.java @@ -35,11 +35,6 @@ public class NullClangHighlightController extends ClangHighlightController { // stub } - @Override - public String getPrimaryHighlightedText() { - return null; - } - @Override public void addPrimaryHighlights(ClangNode parentNode, ColorProvider colorProvider) { // stub @@ -51,7 +46,7 @@ public class NullClangHighlightController extends ClangHighlightController { } @Override - public void addBraceHighlight(ClangSyntaxToken token, Color highlightColor) { + public void addPrimaryHighlightToTokensForBrace(ClangSyntaxToken token, Color highlightColor) { // stub } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenHighlightColors.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenHighlightColors.java index 50624937ed..54438c5c0a 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenHighlightColors.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/TokenHighlightColors.java @@ -25,7 +25,7 @@ import generic.theme.Gui; */ public class TokenHighlightColors { - private Map colorsByName = new HashMap<>(); + private Map colorsByText = new HashMap<>(); private List recentColors = new ArrayList<>(); private Color generateColor() { @@ -42,15 +42,27 @@ public class TokenHighlightColors { } public Color getColor(String text) { - return colorsByName.computeIfAbsent(text, t -> generateColor()); + return colorsByText.computeIfAbsent(text, t -> generateColor()); } public void setColor(String text, Color color) { - colorsByName.put(text, color); + colorsByText.put(text, color); recentColors.add(color); } public List getRecentColors() { return recentColors; } + + public String getAppliedColorsString() { + if (colorsByText.isEmpty()) { + return "No tokens highlighted"; + } + return colorsByText.toString(); + } + + @Override + public String toString() { + return getAppliedColorsString(); + } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/UserHighlights.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/UserHighlights.java new file mode 100644 index 0000000000..c5b1eca91b --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/UserHighlights.java @@ -0,0 +1,148 @@ +/* ### + * 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; + +import java.awt.Color; +import java.util.*; + +import org.apache.commons.collections4.map.LazyMap; + +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.DecompilerHighlighter; +import ghidra.program.model.listing.Function; + +/** + * A class to manage and track Decompiler highlights created by the user via the UI or from a + * script. This class manages secondary and global highlights. For a description of these terms, + * see {@link ClangHighlightController}. + *

    + * These highlights will remain until cleared explicitly by the user or a client API call. + * Contrastingly, context highlights are cleared as the user moves the cursor around the Decompiler + * display. + */ +public class UserHighlights { + + private Map> secondaryHighlightersByFunction = + LazyMap.lazyMap(new HashMap<>(), f -> new ArrayList<>()); + + // store the secondary highlighters here in addition to the map below so that we may discern + // between secondary highlights and highlight service highlights + private Set secondaryHighlighters = new HashSet<>(); + + // all highlighters, including secondary and global highlight service highlighters + private Map allHighlighterHighlights = new HashMap<>(); + + // color supplier for secondary highlights + private TokenHighlightColors secondaryHighlightColors = new TokenHighlightColors(); + + Color getSecondaryColor(String text) { + // Note: this call is used to generate colors for secondary highlighters that this API + // creates. Client highlighters will create their own colors. + return secondaryHighlightColors.getColor(text); + } + + String getAppliedColorsString() { + return secondaryHighlightColors.getAppliedColorsString(); + } + + boolean hasSecondaryHighlights(Function function) { + return !secondaryHighlightersByFunction.get(function).isEmpty(); + } + + Color getSecondaryHighlight(ClangToken token) { + DecompilerHighlighter highlighter = getSecondaryHighlighter(token); + if (highlighter != null) { + TokenHighlights highlights = allHighlighterHighlights.get(highlighter); + HighlightToken hlToken = highlights.get(token); + return hlToken.getColor(); + } + + return null; + } + + TokenHighlightColors getSecondaryHighlightColors() { + return secondaryHighlightColors; + } + + Set getSecondaryHighlighters(Function function) { + return new HashSet<>(secondaryHighlightersByFunction.get(function)); + } + + Set getGlobalHighlighters() { + Set allHighlighters = allHighlighterHighlights.keySet(); + Set results = new HashSet<>(allHighlighters); + results.removeAll(secondaryHighlighters); + return results; + } + + List getSecondaryHighlightersByFunction(Function f) { + return secondaryHighlightersByFunction.get(f); + } + + TokenHighlights getHighlights(DecompilerHighlighter highlighter) { + return allHighlighterHighlights.get(highlighter); + } + + DecompilerHighlighter getSecondaryHighlighter(ClangToken token) { + for (DecompilerHighlighter highlighter : secondaryHighlighters) { + TokenHighlights highlights = allHighlighterHighlights.get(highlighter); + HighlightToken hlToken = highlights.get(token); + if (hlToken != null) { + return highlighter; + } + } + + return null; + } + + void addSecondaryHighlighter(Function function, DecompilerHighlighter highlighter) { + + // Note: this highlighter has likely already been added the the this class, but has not + // yet been bound to the given function. + secondaryHighlightersByFunction.get(function).add(highlighter); + secondaryHighlighters.add(highlighter); + allHighlighterHighlights.putIfAbsent(highlighter, new TokenHighlights()); + } + + // This adds the given highlighter. This is for global and secondary highlights. Secondary + // highlights will be later registered to this class for the function they apply to. + TokenHighlights add(DecompilerHighlighter highlighter) { + allHighlighterHighlights.putIfAbsent(highlighter, new TokenHighlights()); + return allHighlighterHighlights.get(highlighter); + } + + void remove(DecompilerHighlighter highlighter) { + allHighlighterHighlights.remove(highlighter); + secondaryHighlighters.remove(highlighter); + + Collection> lists = secondaryHighlightersByFunction.values(); + for (List highlighters : lists) { + if (highlighters.remove(highlighter)) { + break; + } + } + } + + TokenHighlights get(DecompilerHighlighter highlighter) { + return allHighlighterHighlights.get(highlighter); + } + + void dispose() { + secondaryHighlighters.clear(); + secondaryHighlightersByFunction.clear(); + allHighlighterHighlights.clear(); + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/util/FillOutStructureCmd.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/util/FillOutStructureCmd.java index e5e30c6ae6..fe5d39fae7 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/util/FillOutStructureCmd.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/util/FillOutStructureCmd.java @@ -120,7 +120,7 @@ public class FillOutStructureCmd extends BackgroundCommand { pointerDT = program.getDataTypeManager() .addDataType(pointerDT, DataTypeConflictHandler.DEFAULT_HANDLER); - boolean isThisParam = DecompilerUtils.testForAutoParameterThis(var, function); + boolean isThisParam = DecompilerUtils.isThisParameter(var, function); if (!isThisParam) { commitVariable(var, pointerDT, isThisParam); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/util/FillOutStructureHelper.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/util/FillOutStructureHelper.java index 057e30ad33..c3a548477b 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/util/FillOutStructureHelper.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/util/FillOutStructureHelper.java @@ -132,7 +132,7 @@ public class FillOutStructureHelper { } if (structDT == null) { - if (createClassIfNeeded && DecompilerUtils.testForAutoParameterThis(var, function)) { + if (createClassIfNeeded && DecompilerUtils.isThisParameter(var, function)) { structDT = createUniqueClassNamespaceAndStructure(var, (int) size, function); } else { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java index 9c1dd91bba..a43259d00a 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java @@ -1016,6 +1016,13 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter new RemoveAllSecondaryHighlightsAction(); setGroupInfo(removeAllSecondadryHighlightsAction, highlightGroup, subGroupPosition++); + PreviousHighlightedTokenAction previousHighlightedTokenAction = + new PreviousHighlightedTokenAction(); + setGroupInfo(previousHighlightedTokenAction, highlightGroup, subGroupPosition++); + + NextHighlightedTokenAction nextHighlightedTokenAction = new NextHighlightedTokenAction(); + setGroupInfo(nextHighlightedTokenAction, highlightGroup, subGroupPosition++); + String convertGroup = "7 - Convert Group"; subGroupPosition = 0; RemoveEquateAction removeEquateAction = new RemoveEquateAction(); @@ -1122,6 +1129,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter addLocalAction(setSecondaryHighlightColorChooserAction); addLocalAction(removeSecondaryHighlightAction); addLocalAction(removeAllSecondadryHighlightsAction); + addLocalAction(nextHighlightedTokenAction); + addLocalAction(previousHighlightedTokenAction); addLocalAction(convertBinaryAction); addLocalAction(convertDecAction); addLocalAction(convertFloatAction); @@ -1165,7 +1174,14 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter private void setGroupInfo(DockingAction action, String group, int subGroupPosition) { MenuData popupMenuData = action.getPopupMenuData(); popupMenuData.setMenuGroup(group); - popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition)); + + // Some groups have numbers reach double-digits. These will not compare correctly unless + // padded. Ensure all string numbers are at least 2 digits. + String numberString = Integer.toString(subGroupPosition); + if (numberString.length() == 1) { + numberString = '0' + numberString; + } + popupMenuData.setMenuSubGroup(numberString); } private void graphServiceRemoved() { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/DecompilerStructureVariableAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/DecompilerStructureVariableAction.java index b7aa780f65..9316429f70 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/DecompilerStructureVariableAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/DecompilerStructureVariableAction.java @@ -61,7 +61,7 @@ public class DecompilerStructureVariableAction extends CreateStructureVariableAc HighVariable var = tokenAtCursor.getHighVariable(); if (var != null && !(var instanceof HighConstant)) { dt = var.getDataType(); - isThisParam = DecompilerUtils.testForAutoParameterThis(var, function); + isThisParam = DecompilerUtils.isThisParameter(var, function); } if (dt == null || dt.getLength() > maxPointerSize) { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/NextHighlightedTokenAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/NextHighlightedTokenAction.java new file mode 100644 index 0000000000..e1e9b265e0 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/NextHighlightedTokenAction.java @@ -0,0 +1,99 @@ +/* ### + * 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.plugin.core.decompile.actions; + +import java.util.List; + +import docking.action.KeyBindingData; +import docking.action.MenuData; +import docking.widgets.fieldpanel.field.Field; +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.TokenIterator; +import ghidra.app.decompiler.component.*; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.util.HelpTopics; +import ghidra.util.HelpLocation; + +/** + * An action to navigate to the next token highlighted by the user via the middle-mouse. + */ +public class NextHighlightedTokenAction extends AbstractDecompilerAction { + + public NextHighlightedTokenAction() { + super("Next Highlihted Token"); + + setPopupMenuData(new MenuData(new String[] { "Next Highlight" }, "Decompile")); + setKeyBindingData(new KeyBindingData("Ctrl period")); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "GoToMiddleMouseHighlight")); + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + if (!context.hasRealFunction()) { + return false; + } + DecompilerPanel panel = context.getDecompilerPanel(); + TokenHighlights highlights = panel.getMiddleMouseHighlights(); + if (highlights != null) { + return highlights.size() > 1; + } + return false; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + + DecompilerPanel panel = context.getDecompilerPanel(); + TokenHighlights highlights = panel.getMiddleMouseHighlights(); + ClangToken cursorToken = context.getTokenAtCursor(); + TokenIterator it = new TokenIterator(cursorToken, true); + it.next(); // ignore the current token + + if (goToNexToken(panel, it, highlights)) { + return; // found another token in the current direction + } + + // this means there are no more occurrences in the current direction; wrap the search + ClangToken firstToken = getFirstToken(panel); + it = new TokenIterator(firstToken, true); + goToNexToken(panel, it, highlights); + } + + private ClangToken getFirstToken(DecompilerPanel panel) { + List fields = panel.getFields(); + Field line = fields.get(0); + ClangTextField tf = (ClangTextField) line; + return tf.getFirstToken(); + } + + private boolean goToNexToken(DecompilerPanel panel, TokenIterator it, + TokenHighlights highlights) { + + while (it.hasNext()) { + ClangToken nextToken = it.next(); + HighlightToken hlToken = highlights.get(nextToken); + if (hlToken == null) { + continue; + } + + ClangToken token = hlToken.getToken(); + panel.goToToken(token); + return true; + } + + return false; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/PreviousHighlightedTokenAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/PreviousHighlightedTokenAction.java new file mode 100644 index 0000000000..0fa016f118 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/PreviousHighlightedTokenAction.java @@ -0,0 +1,100 @@ +/* ### + * 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.plugin.core.decompile.actions; + +import java.util.List; + +import docking.action.KeyBindingData; +import docking.action.MenuData; +import docking.widgets.fieldpanel.field.Field; +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.TokenIterator; +import ghidra.app.decompiler.component.*; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.util.HelpTopics; +import ghidra.util.HelpLocation; + +/** + * An action to navigate to the previous token highlighted by the user via the middle-mouse. + */ +public class PreviousHighlightedTokenAction extends AbstractDecompilerAction { + + public PreviousHighlightedTokenAction() { + super("Previous Highlighted Token"); + + setPopupMenuData(new MenuData(new String[] { "Previous Highlight" }, "Decompile")); + setKeyBindingData(new KeyBindingData("Ctrl comma")); + setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "GoToMiddleMouseHighlight")); + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + if (!context.hasRealFunction()) { + return false; + } + DecompilerPanel panel = context.getDecompilerPanel(); + TokenHighlights highlights = panel.getMiddleMouseHighlights(); + if (highlights != null) { + return highlights.size() > 1; + } + return false; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + + DecompilerPanel panel = context.getDecompilerPanel(); + TokenHighlights highlights = panel.getMiddleMouseHighlights(); + ClangToken cursorToken = context.getTokenAtCursor(); + TokenIterator it = new TokenIterator(cursorToken, false); + it.next(); // ignore the current token + + if (goToNexToken(panel, it, highlights)) { + return; // found another token in the current direction + } + + // this means there are no more occurrences in the current direction; wrap the search + ClangToken lastToken = getLastToken(panel); + it = new TokenIterator(lastToken, false); + goToNexToken(panel, it, highlights); + } + + private ClangToken getLastToken(DecompilerPanel panel) { + List fields = panel.getFields(); + int lastLine = fields.size(); + Field line = fields.get(lastLine - 1); + ClangTextField tf = (ClangTextField) line; + return tf.getLastToken(); + } + + private boolean goToNexToken(DecompilerPanel panel, TokenIterator it, + TokenHighlights highlights) { + + while (it.hasNext()) { + ClangToken nextToken = it.next(); + HighlightToken hlToken = highlights.get(nextToken); + if (hlToken == null) { + continue; + } + + ClangToken token = hlToken.getToken(); + panel.goToToken(token); + return true; + } + + return false; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SliceHighlightColorProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SliceHighlightColorProvider.java index 81bacbcc4e..11a9724e38 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SliceHighlightColorProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SliceHighlightColorProvider.java @@ -70,4 +70,9 @@ public class SliceHighlightColorProvider implements ColorProvider { } return c; } + + @Override + public String toString() { + return "Slice Color Provider " + hlColor; + } } diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java index 0160de996e..c5e56164d2 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java @@ -46,6 +46,7 @@ import ghidra.app.plugin.core.decompile.actions.*; import ghidra.app.util.AddEditDialog; import ghidra.framework.options.ToolOptions; import ghidra.program.model.listing.CodeUnit; +import ghidra.util.Msg; public class DecompilerClangTest extends AbstractDecompilerTest { @@ -383,29 +384,32 @@ public class DecompilerClangTest extends AbstractDecompilerTest { setDecompilerLocation(line, charPosition); ClangToken token = getToken(); - String text = token.getText(); - assertEquals("_printf", text); + String printfText = token.getText(); + assertEquals("_printf", printfText); - highlight(); + highlight(); // "printf" is secondary highlighted // 5:30 "a->name" line = 5; charPosition = 38; setDecompilerLocation(line, charPosition); ClangToken token2 = getToken(); - String text2 = token2.getText(); - assertEquals("name", text2); + String nameText = token2.getText(); + assertEquals("name", nameText); - Color color2 = highlight(); + Color color2 = highlight(); // "name" is secondary highlighted // 5:2 "_printf" line = 5; charPosition = 2; setDecompilerLocation(line, charPosition); - removeSecondaryHighlight(); - assertNoFieldsSecondaryHighlighted(text); - assertAllFieldsHighlighted(text2, color2); + Msg.debug(this, "test - remove"); + + removeSecondaryHighlight(); // remove "printf" highlight + + assertNoFieldsSecondaryHighlighted(printfText); + assertAllFieldsHighlighted(nameText, color2); } @Test @@ -812,7 +816,106 @@ public class DecompilerClangTest extends AbstractDecompilerTest { } @Test - public void testSecondaryHighlighting_MiddleMouseDoesNotClearSecondaryHighlight() { + public void testSecondaryHighlighting_MiddleMouse_SecondaryHighlight() { + + /* + + The middle mouse is a secondary highlight so that it persists as the user clicks around. + + Middle mousing on an already middle moused highlight should clear the highlight. Middle + mousing on a new token should clear the original highlight and highlight the new token. + + Decomp of '_call_structure_A': + + 1| + 2| void _call_structure_A(A *a) + 3| + 4| { + 5| _printf("call_structure_A: %s\n",a->name); + 6| _printf("call_structure_A: %s\n",(a->b).name); + 7| _printf("call_structure_A: %s\n",(a->b).c.name); + 8| _printf("call_structure_A: %s\n",(a->b).c.d.name); + 9| _printf("call_structure_A: %s\n",(a->b).c.d.e.name); + 10| _call_structure_B(&a->b); + 11| return; + 12| } + + */ + + decompile("100000d60"); // '_call_structure_A' + + // 5:2 "_printf" + int line = 5; + int charPosition = 2; + setDecompilerLocation(line, charPosition); + + ClangToken token = getToken(); + String tokenText = token.getText(); + assertEquals("_printf", tokenText); + + middleMouse(); + assertCombinedHighlightColor(token); + + middleMouse(); + assertNoFieldsSecondaryHighlighted(tokenText); + } + + @Test + public void testSecondaryHighlighting_MiddleMouse_SecondaryHighlight_NewToken() { + + /* + + The middle mouse is a secondary highlight so that it persists as the user clicks around. + + Middle mousing on an already middle moused highlight should clear the highlight. Middle + mousing on a new token should clear the original highlight and highlight the new token. + + Decomp of '_call_structure_A': + + 1| + 2| void _call_structure_A(A *a) + 3| + 4| { + 5| _printf("call_structure_A: %s\n",a->name); + 6| _printf("call_structure_A: %s\n",(a->b).name); + 7| _printf("call_structure_A: %s\n",(a->b).c.name); + 8| _printf("call_structure_A: %s\n",(a->b).c.d.name); + 9| _printf("call_structure_A: %s\n",(a->b).c.d.e.name); + 10| _call_structure_B(&a->b); + 11| return; + 12| } + + */ + + decompile("100000d60"); // '_call_structure_A' + + // 5:2 "_printf" + int line = 5; + int charPosition = 2; + setDecompilerLocation(line, charPosition); + + ClangToken token = getToken(); + String tokenText = token.getText(); + assertEquals("_printf", tokenText); + + middleMouse(); + assertCombinedHighlightColor(token); + + // 5:30 "a->name" + line = 5; + charPosition = 38; + setDecompilerLocation(line, charPosition); + ClangToken token2 = getToken(); + String text2 = token2.getText(); + assertEquals("name", text2); + + middleMouse(); + assertNoFieldsSecondaryHighlighted(tokenText); + assertCombinedHighlightColor(token2); + } + + @Test + public void testSecondaryHighlighting_MiddleMouseDoesNotClearSecondaryHighlight_ExistingHighlight() { /* @@ -850,7 +953,9 @@ public class DecompilerClangTest extends AbstractDecompilerTest { assertCombinedHighlightColor(token); middleMouse(); - assertAllFieldsHighlighted(tokenText, color); + ClangToken cursorToken = getToken(provider); + Predicate ignores = t -> t == cursorToken; + assertAllFieldsHighlighted(tokenText, color, ignores); } @Test