diff --git a/Ghidra/Features/Base/src/main/help/help/topics/GhidraScriptMgrPlugin/ScriptDevelopment.htm b/Ghidra/Features/Base/src/main/help/help/topics/GhidraScriptMgrPlugin/ScriptDevelopment.htm index d949399ba7..84eb0f34f1 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/GhidraScriptMgrPlugin/ScriptDevelopment.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/GhidraScriptMgrPlugin/ScriptDevelopment.htm @@ -128,9 +128,16 @@
The tag indicates the top-level menu path. Path levels are delimited using the "." - character.
+ character. A mnemonic can be defined by adding an ampersand ("&") in front of the mnemonic + key. Ampersands can be escaped by adding another ampersand ("&&"). -For example, "@menupath File.Run.My Script".
+For example:
++@menupath File.Run.My Script
+
+ @menupath File.Run.My &Script
+ @menupath File.Run.Me && My &Script
+
@toolbar
* NOTE: do NOT use this method with strings that contain user-supplied text. Instead, use @@ -305,7 +305,12 @@ public class MenuData { } private static int getMnemonic(String string) { - int indexOf = string.indexOf('&'); + int indexOf; + int fromIndex = 0; + do { + indexOf = string.indexOf('&', fromIndex); + fromIndex = indexOf + 2; + } while (indexOf >= 0 && indexOf < string.length() - 1 && string.charAt(indexOf + 1) == '&'); if (indexOf >= 0 && indexOf < string.length() - 1) { return string.charAt(indexOf + 1); } @@ -321,7 +326,18 @@ public class MenuData { } private static String processMenuItemName(String string) { - return string.replaceFirst("&", ""); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < string.length(); i++) { + if (string.charAt(i) == '&') { + if (i < string.length() - 1 && string.charAt(i+1) == '&') { + builder.append('&'); + i++; + } + } else { + builder.append(string.charAt(i)); + } + } + return builder.toString(); } public String getMenuItemName() { diff --git a/Ghidra/Framework/Docking/src/test/java/docking/action/MenuDataTest.java b/Ghidra/Framework/Docking/src/test/java/docking/action/MenuDataTest.java index ad6ada3eaa..400e2e0bb9 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/action/MenuDataTest.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/action/MenuDataTest.java @@ -34,6 +34,45 @@ public class MenuDataTest { MenuData menuData = new MenuData(new String[] { "One", "Two", "&Three" }); assertEquals(menuData.getMnemonic(), 'T'); } + + /** + * There should be no mnemonic, the ampersand is escaped. + */ + @Test + public void testMenuDataMnemonicSkipsEscapedAmpersand() { + MenuData menuData = new MenuData(new String[] { "One", "Two", "&&Three" }); + assertEquals(menuData.getMnemonic(), MenuData.NO_MNEMONIC); + } + + /** + * The mnemonic should be 'h'. The first two ampersands form an escaped + * ampersand. The third ampersand is not escaped. + */ + @Test + public void testMenuDataMnemonicEscapesAmpersandLeftToRight() { + MenuData menuData = new MenuData(new String[] { "One", "Two", "T&&&hree" }); + assertEquals(menuData.getMnemonic(), 'h'); + } + + /** + * There should be no mnemonic, the trailing ampersand is not followed by any + * character. + */ + @Test + public void testMenuDataMnemonicIgnoresTrailingAmpersand() { + MenuData menuData = new MenuData(new String[] { "One", "Two", "Three&" }); + assertEquals(menuData.getMnemonic(), MenuData.NO_MNEMONIC); + } + + /** + * The mnemonic should be 'T'. This is the expected behaviour as per the + * "Desktop development with C++" workload in Visual Studio. + */ + @Test + public void testMenuDataMnemonicParsesLeftToRight() { + MenuData menuData = new MenuData(new String[] { "One", "Two", "&T&hree" }); + assertEquals(menuData.getMnemonic(), 'T'); + } /** * Mnemonic should be 'h' based on the value that was explicitly set @@ -86,4 +125,23 @@ public class MenuDataTest { menuData.setMenuPath(newPath); assertEquals(menuData.getMnemonic(), MenuData.NO_MNEMONIC); } + + @Test + public void testGetMenuItemNameEscapesAmpersand() { + MenuData menuData = new MenuData(new String[] { "One", "Two", "&&Three" }); + assertEquals(menuData.getMenuItemName(), "&Three"); + } + + /** + * Ampersands that are not escaped should be ignored regardless of use as + * mnemonics. + */ + @Test + public void testGetMenuItemNameIgnoresUnescapedAmpersand() { + MenuData menuData = new MenuData(new String[] { "One", "Two", "Three&" }); + assertEquals(menuData.getMenuItemName(), "Three"); + + menuData.setMenuItemName("&T&hree"); + assertEquals(menuData.getMenuItemName(), "Three"); + } }