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