ui: rewrite KeymapsTestCase

* detect duplicated in mouse shortcuts
* detect removed duplicates
* do not repeat same duplicates in dependent keymaps
* require to specify all duplicated shortcuts for actions pair
* detect duplicates between second-stroke and single-stroke shortcuts
This commit is contained in:
Aleksey Pivovarov
2017-05-24 20:04:06 +03:00
committed by Aleksey Pivovarov
parent 603a1620fe
commit 9fffa74cf5
5 changed files with 319 additions and 357 deletions
@@ -300,6 +300,49 @@ public class KeymapUtil {
return new MouseShortcut(button, modifiers, clickCount);
}
/**
* @return string representation of passed mouse shortcut. This method should
* be used only for serializing of the <code>MouseShortcut</code>
*/
public static String getMouseShortcutString(MouseShortcut shortcut) {
if (Registry.is("ide.mac.forceTouch") && shortcut instanceof PressureShortcut) {
return "Force touch";
}
StringBuilder buffer = new StringBuilder();
// modifiers
int modifiers = shortcut.getModifiers();
if ((InputEvent.SHIFT_DOWN_MASK & modifiers) > 0) {
buffer.append(SHIFT);
buffer.append(' ');
}
if ((InputEvent.CTRL_DOWN_MASK & modifiers) > 0) {
buffer.append(CONTROL);
buffer.append(' ');
}
if ((InputEvent.META_DOWN_MASK & modifiers) > 0) {
buffer.append(META);
buffer.append(' ');
}
if ((InputEvent.ALT_DOWN_MASK & modifiers) > 0) {
buffer.append(ALT);
buffer.append(' ');
}
if ((InputEvent.ALT_GRAPH_DOWN_MASK & modifiers) > 0) {
buffer.append(ALT_GRAPH);
buffer.append(' ');
}
// button
buffer.append("button").append(shortcut.getButton()).append(' ');
if (shortcut.getClickCount() > 1) {
buffer.append(DOUBLE_CLICK);
}
return buffer.toString().trim(); // trim trailing space (if any)
}
public static String getKeyModifiersTextForMacOSLeopard(@JdkConstants.InputEventMask int modifiers) {
StringBuilder buf = new StringBuilder();
if ((modifiers & InputEvent.META_MASK) != 0) {
@@ -28,7 +28,6 @@ import com.intellij.openapi.options.ExternalizableSchemeAdapter
import com.intellij.openapi.options.SchemeState
import com.intellij.openapi.util.InvalidDataException
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.registry.Registry
import com.intellij.ui.KeyStrokeAdapter
import com.intellij.util.ArrayUtil
import com.intellij.util.ArrayUtilRt
@@ -38,7 +37,6 @@ import com.intellij.util.containers.mapSmart
import com.intellij.util.containers.nullize
import gnu.trove.THashMap
import org.jdom.Element
import java.awt.event.InputEvent
import java.util.*
import javax.swing.KeyStroke
@@ -589,7 +587,7 @@ open class KeymapImpl @JvmOverloads constructor(private var dataHolder: SchemeDa
}
is MouseShortcut -> {
val element = Element(MOUSE_SHORTCUT)
element.setAttribute(KEYSTROKE_ATTRIBUTE, getMouseShortcutString(shortcut))
element.setAttribute(KEYSTROKE_ATTRIBUTE, KeymapUtil.getMouseShortcutString(shortcut))
actionElement.addContent(element)
}
is KeyboardModifierGestureShortcut -> {
@@ -711,47 +709,4 @@ private fun areShortcutsEqual(shortcuts1: List<Shortcut>, shortcuts2: List<Short
}
}
return true
}
/**
* @return string representation of passed mouse shortcut. This method should
* * be used only for serializing of the `MouseShortcut`
*/
private fun getMouseShortcutString(shortcut: MouseShortcut): String {
if (Registry.`is`("ide.mac.forceTouch") && shortcut is PressureShortcut) {
return "Force touch"
}
val buffer = StringBuilder()
// modifiers
val modifiers = shortcut.modifiers
if (InputEvent.SHIFT_DOWN_MASK and modifiers > 0) {
buffer.append("shift")
buffer.append(' ')
}
if (InputEvent.CTRL_DOWN_MASK and modifiers > 0) {
buffer.append("control")
buffer.append(' ')
}
if (InputEvent.META_DOWN_MASK and modifiers > 0) {
buffer.append("meta")
buffer.append(' ')
}
if (InputEvent.ALT_DOWN_MASK and modifiers > 0) {
buffer.append("alt")
buffer.append(' ')
}
if (InputEvent.ALT_GRAPH_DOWN_MASK and modifiers > 0) {
buffer.append("altGraph")
buffer.append(' ')
}
// button
buffer.append("button").append(shortcut.button).append(' ')
if (shortcut.clickCount > 1) {
buffer.append("doubleClick")
}
return buffer.toString().trim { it <= ' ' } // trim trailing space (if any)
}
@@ -30,7 +30,10 @@ class MacOSDefaultKeymap(dataHolder: SchemeDataHolder<KeymapImpl>, defaultKeymap
if (shortcut is MouseShortcut) {
return _convertMouseShortcut(shortcut)
}
return KeyboardShortcut(_convertKeyStroke((shortcut as KeyboardShortcut).firstKeyStroke), shortcut.secondKeyStroke?.let(::_convertKeyStroke))
if (shortcut is KeyboardShortcut) {
return KeyboardShortcut(_convertKeyStroke(shortcut.firstKeyStroke), shortcut.secondKeyStroke?.let(::_convertKeyStroke))
}
return shortcut
}
}
@@ -31,7 +31,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "BACK_SPACE", "EditorBackSpace", "Images.Thumbnails.UpFolder"},
{ "ENTER", "EditorChooseLookupItem", "NextTemplateVariable", "EditorEnter", "Images.Thumbnails.EnterAction",
"PropertyInspectorActions.EditValue", "Console.Execute", "Console.TableResult.EditValue"},
{ "F2", "GotoNextError", "GuiDesigner.EditComponent", "GuiDesigner.EditGroup", "Console.TableResult.EditValue", "XDebugger.SetValue", "XDebugger.EditWatch", "Arrangement.Rule.Edit"},
{ "F2", "GotoNextError", "GuiDesigner.EditComponent", "GuiDesigner.EditGroup", "Console.TableResult.EditValue", "XDebugger.SetValue", "Arrangement.Rule.Edit"},
{ "alt ENTER", "ShowIntentionActions", "Console.TableResult.EditValue", "DatabaseView.PropertiesAction"},
{ "F5", "UML.ApplyCurrentLayout", "CopyElement"},
{ "F7", "NextDiff", "StepInto"},
@@ -57,10 +57,11 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "control M", "EditorScrollToCenter", "Vcs.ShowMessageHistory"},
{ "control N", "FileChooser.NewFolder", "GotoClass", "GotoChangedFile"},
{ "control P", "FileChooser.TogglePathShowing", "ParameterInfo"},
{ "control R", "Replace", "Console.TableResult.Reload", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{ "control R", "Replace", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{ "control SLASH", "CommentByLineComment", "Images.Editor.ActualSize"},
{ "control U", "GotoSuperMethod", "CommanderSwapPanels"},
{ "control UP", "EditorScrollUp", "EditorLookupUp", "MethodOverloadSwitchUp"},
{ "control SUBTRACT", "CollapseAll", "CollapseRegion"},
{ "control alt A", "ChangesView.AddUnversioned", "Diagram.DeselectAll"},
{ "control alt E", "PerforceDirect.Edit", "Console.History.Browse"},
{ "control alt DOWN", "NextOccurence", "Console.TableResult.NextPage"},
@@ -88,8 +89,6 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "shift control RIGHT", "EditorNextWordWithSelection", "ResizeToolWindowRight", },
{ "shift control T", "GotoTest", "Images.ShowThumbnails"},
{ "shift control UP", "ResizeToolWindowUp", "MoveStatementUp"},
{ "shift control alt DOWN", "VcsShowNextChangeMarker", "HtmlTableCellNavigateDown"},
{ "shift control alt UP", "VcsShowPrevChangeMarker", "HtmlTableCellNavigateUp"},
{ "shift control alt D", "UML.ShowChanges", "Console.TableResult.CloneColumn"},
{ "shift control U", "ShelveChanges.UnshelveWithDialog", "EditorToggleCase"},
{ "control E", "RecentFiles", "Vcs.ShowMessageHistory"},
@@ -100,7 +99,6 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "ctrl alt ENTER", "EditorStartNewLineBefore", "QuickActionPopup"},
});
put("Mac OS X 10.5+", new String[][] {
{ "F5", "CopyElement", "Console.TableResult.Reload", "UML.ApplyCurrentLayout"},
{ "BACK_SPACE", "$Delete", "EditorBackSpace", "Images.Thumbnails.UpFolder"},
{ "shift BACK_SPACE", "EditorBackSpace", "UsageView.Include"},
{ "meta BACK_SPACE", "EditorDeleteLine", "$Delete"},
@@ -122,7 +120,6 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "shift meta G", "ClassTemplateNavigation", "GoToClass", "FindPrevious"},
{ "shift meta LEFT", "EditorLineStartWithSelection", "ResizeToolWindowLeft", },
{ "shift meta RIGHT", "EditorLineEndWithSelection", "ResizeToolWindowRight", },
{ "meta E", "RecentFiles", "Vcs.ShowMessageHistory"},
{ "alt R", "Django.RunManageTaskAction", "org.jetbrains.plugins.ruby.tasks.rake.actions.RakeTasksPopupAction"},
{ "alt DOWN", "EditorUnSelectWord", "Arrangement.Rule.Match.Condition.Move.Down"},
{ "alt UP", "EditorSelectWord", "Arrangement.Rule.Match.Condition.Move.Up"},
@@ -135,79 +132,62 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "control ENTER", "Generate", "NewElement"},
{ "control F5", "Refresh", "Rerun"},
{ "control TAB", "Switcher", "Diff.FocusOppositePane"},
{ "control N", "Generate", "NewElement"},
{ "shift control TAB", "Switcher", "Diff.FocusOppositePaneAndScroll"},
{ "meta 1", "ActivateProjectToolWindow", "FileChooser.GotoHome", "DuplicatesForm.SendToLeft"},
{ "meta 2", "ActivateFavoritesToolWindow", "FileChooser.GotoProject", "DuplicatesForm.SendToRight"},
{ "meta 3", "ActivateFindToolWindow", "FileChooser.GotoModule"},
{ "meta E", "RecentFiles", "Vcs.ShowMessageHistory"},
{ "shift meta LEFT", "EditorLineStartWithSelection", "ResizeToolWindowLeft", },
{ "shift meta RIGHT", "EditorLineEndWithSelection", "ResizeToolWindowRight", },
{ "alt R", "Django.RunManageTaskAction", "org.jetbrains.plugins.ruby.tasks.rake.actions.RakeTasksPopupAction"},
});
put("Emacs", new String[][] {
{ "ENTER", "EditorChooseLookupItem", "NextTemplateVariable", "EditorEnter", "Images.Thumbnails.EnterAction",
"PropertyInspectorActions.EditValue", "Console.Execute", "Console.TableResult.EditValue"},
{ "F2", "GotoNextError", "GuiDesigner.EditComponent", "GuiDesigner.EditGroup", "Console.TableResult.EditValue", "XDebugger.SetValue", "XDebugger.EditWatch", "Arrangement.Rule.Edit"},
{ "alt ENTER", "ShowIntentionActions", "Console.TableResult.EditValue", "DatabaseView.PropertiesAction"},
{ "TAB", "EditorChooseLookupItemReplace", "NextTemplateVariable", "NextParameter", "EditorIndentSelection",
{ "TAB", "EditorChooseLookupItemReplace", "NextTemplateVariable", "NextParameter", "EditorIndentSelection",
"EmacsStyleIndent", "NextTemplateParameter", "ExpandLiveTemplateByTab"},
{ "alt DOWN", "ShowContent", "MethodDown", "Arrangement.Rule.Match.Condition.Move.Down"},
{ "alt UP", "MethodUp", "Arrangement.Rule.Match.Condition.Move.Up"},
{ "alt SLASH", "CodeCompletion", "HippieCompletion"},
{ "control 1", "FileChooser.GotoHome", "GotoBookmark1", "DuplicatesForm.SendToLeft"},
{ "control 2", "FileChooser.GotoProject", "GotoBookmark2", "DuplicatesForm.SendToRight"},
{ "control 3", "GotoBookmark3", "FileChooser.GotoModule"},
{ "control D", "$Delete", "Diff.ShowDiff", "CompareTwoFiles", "SendEOF", "FileChooser.GotoDesktop"},
{ "control K", "EditorCutLineEnd", "CheckinProject"},
{ "control L", "EditorScrollToCenter", "Vcs.Log.FocusTextFilter"},
{ "control M", "EditorEnter", "EditorChooseLookupItem", "NextTemplateVariable", "Console.Execute"},
{ "control N", "EditorDown", "FileChooser.NewFolder"},
{ "control P", "EditorUp", "FileChooser.TogglePathShowing"},
{ "control R", "Console.TableResult.Reload", "org.jetbrains.plugins.ruby.rails.console.ReloadSources", "FindPrevious"},
{ "control R", "org.jetbrains.plugins.ruby.rails.console.ReloadSources", "FindPrevious"},
{ "control SLASH", "$Undo", "Images.Editor.ActualSize"},
{ "control X", "GotoFile", "SaveAll", "NextTab", "PreviousTab", "CloseContent", "CloseAllEditors", "NextSplitter",
"GotoNextError", "NextProjectWindow", "EditorSwapSelectionBoundaries", "SplitVertically",
"SplitHorizontally", "UnsplitAll", "Switcher", "$SelectAll"},
{ "control UP", "EditorBackwardParagraph", "EditorLookupUp", "MethodOverloadSwitchUp"},
{ "control DOWN", "EditorForwardParagraph", "EditorLookupDown", "MethodOverloadSwitchDown"},
{ "control alt A", "MethodUp", "ChangesView.AddUnversioned", "Diagram.DeselectAll"},
{ "control alt E", "MethodDown", "PerforceDirect.Edit", "Console.History.Browse"},
{ "control alt G", "GotoDeclaration", "org.jetbrains.plugins.ruby.rails.actions.generators.GeneratorsPopupAction", "Mvc.RunTarget"},
{ "control alt S", "ShowSettings", "Find"},
{ "shift DELETE", "$Cut", "Maven.Uml.Exclude"},
{ "shift alt S", "FindUsages", "context.save"},
{ "shift alt G", "GotoChangedFile", "GotoClass", "hg4idea.QGotoFromPatches"},
{ "shift alt P", "ParameterInfo", "hg4idea.QPushAction"},
{ "control alt I", "DatabaseView.GenerateScriptIntoConsole", "AutoIndentLines"},
{ "shift control X", "GotoPreviousError", "com.jetbrains.php.framework.FrameworkRunConsoleAction"},
{ "shift control X", SECOND_STROKE, "com.jetbrains.php.framework.FrameworkRunConsoleAction"},
});
put("Visual Studio", new String[][] {
{ "F5", "Resume", "UML.ApplyCurrentLayout"},
{ "F7", "NextDiff", "CompileDirty"},
{ "alt F2", "ShowBookmarks", "WebOpenInAction"},
{ "alt F8", "ReformatCode", "ForceStepInto", "EvaluateExpression"},
{ "alt INSERT", "FileChooser.NewFolder", "Generate", "NewElement"},
{ "control DIVIDE", "CommentByLineComment", "Images.Editor.ActualSize"},
{ "control COMMA", "GotoClass", "GotoChangedFile"},
{ "control F1", "ExternalJavaDoc", "ShowErrorDescription"},
{ "control F10", "RunToCursor", "javaee.UpdateRunningApplication", "liveedit.UpdateRunningApplication"},
{ "control F5", "Rerun", "Run"},
{ "control N", "FileChooser.NewFolder", "Generate", },
{ "control P", "FileChooser.TogglePathShowing", "Print"},
{ "control SLASH", "CommentByLineComment", "Images.Editor.ActualSize"},
{ "control alt F", "ReformatCode", "IntroduceField"},
{ "shift F1", "QuickJavaDoc", "ExternalJavaDoc"},
{ "shift F12", "RestoreDefaultLayout", "FindUsagesInFile"},
{ "shift F2", "GotoPreviousError", "GotoDeclaration"},
{ "shift control F7", "FindUsagesInFile", "HighlightUsagesInFile"},
{ "shift control I", "ImplementMethods", "QuickImplementations"},
{ "alt F9", "ViewBreakpoints", "EditBreakpoint"},
{ "alt MULTIPLY", "ShowExecutionPoint", "Images.Thumbnails.ToggleRecursive"},
{ "button2", "EditorPasteFromX11", "GotoDeclaration" },
});
put("Default for XWin", new String[][] {
{ "shift ctrl alt button1", "EditorAddRectangularSelectionOnMouseDrag", "QuickEvaluateExpression"},
});
put("Default for GNOME", new String[][] {
{ "alt F1", "SelectIn", "ProjectViewChangeView"},
{ "shift alt 1", "SelectIn", "ProjectViewChangeView"},
{ "shift alt 7", "IDEtalk.SearchUserHistory", "FindUsages"},
{ "shift alt LEFT", "PreviousEditorTab", "Back"},
@@ -223,10 +203,8 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "shift alt L", "ReformatCode", "org.jetbrains.plugins.ruby.console.LoadInIrbConsoleAction", "context.load"},
});
put("Eclipse", new String[][] {
{ "F2", "Console.TableResult.EditValue", "QuickJavaDoc", "XDebugger.SetValue", "XDebugger.EditWatch", "Arrangement.Rule.Edit"},
{ "alt ENTER", "ShowIntentionActions", "Console.TableResult.EditValue", "DatabaseView.PropertiesAction"},
{ "F2", "Console.TableResult.EditValue", "QuickJavaDoc", "XDebugger.SetValue", "Arrangement.Rule.Edit"},
{ "F5", "UML.ApplyCurrentLayout", "StepInto"},
{ "TAB", "EditorChooseLookupItemReplace", "NextTemplateVariable", "NextParameter", "EditorIndentSelection", "EditorTab", "NextTemplateParameter", "ExpandLiveTemplateByTab"},
{ "alt DOWN", "ShowContent", "MoveStatementDown", "Arrangement.Rule.Match.Condition.Move.Down"},
{ "alt UP", "MoveStatementUp", "Arrangement.Rule.Match.Condition.Move.Up"},
{ "alt HOME", "ViewNavigationBar", "ShowNavBar"},
@@ -235,11 +213,10 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "control L", "Vcs.Log.FocusTextFilter", "GotoLine"},
{ "control N", "ShowPopupMenu", "FileChooser.NewFolder"},
{ "control P", "FileChooser.TogglePathShowing", "Print"},
{ "control R", "RunToCursor", "Console.TableResult.Reload", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{ "control R", "RunToCursor", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{ "control U", "EvaluateExpression", "CommanderSwapPanels"},
{ "control alt DOWN", "Console.TableResult.NextPage", "EditorDuplicateLines"},
{ "control alt E", "Console.History.Browse", "ExecuteInPyConsoleAction", "PerforceDirect.Edit"},
{ "control alt G", "org.jetbrains.plugins.ruby.rails.actions.generators.GeneratorsPopupAction", "Mvc.RunTarget"},
{ "shift alt D", "hg4idea.QFold", "Debug"},
{ "shift alt G", "RerunTests", "hg4idea.QGotoFromPatches"},
{ "shift alt L", "IntroduceVariable", "org.jetbrains.plugins.ruby.console.LoadInIrbConsoleAction", "context.load"},
@@ -247,36 +224,30 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "shift alt S", "ShowPopupMenu", "context.save"},
{ "shift alt T", "ShowPopupMenu", "tasks.switch"},
{ "shift control DOWN", "ResizeToolWindowDown", "MethodDown"},
{ "shift control E", "EditSource", "RecentChangedFiles", "Graph.Faces.OpenSelectedPages"},
{ "shift control E", "EditSource", "RecentChangedFiles"},
{ "shift control F6", "PreviousTab", "ChangeTypeSignature"},
{ "shift control F11", "ToggleBookmark", "FocusTracer"},
{ "shift control G", "FindUsagesInFile", "ClassTemplateNavigation", "GoToClass"},
{ "shift control I", "QuickImplementations", "XDebugger.Inspect"},
{ "shift control LEFT", "EditorPreviousWordWithSelection", "ResizeToolWindowLeft", },
{ "shift control RIGHT", "EditorNextWordWithSelection", "ResizeToolWindowRight", },
{ "shift control UP", "ResizeToolWindowUp", "MethodUp"},
{ "shift control K", "Vcs.Push", "FindPrevious"},
{ "shift control X", "EditorToggleCase", "com.jetbrains.php.framework.FrameworkRunConsoleAction"},
{ "shift control U", "ShelveChanges.UnshelveWithDialog", "EditorToggleCase"},
{ "shift control T", "GotoClass", "GotoChangedFile"},
});
put("NetBeans 6.5", new String[][] {
{ "F2", "GotoNextError", "GuiDesigner.EditComponent", "GuiDesigner.EditGroup", "Console.TableResult.EditValue", "XDebugger.SetValue", "XDebugger.EditWatch", "Arrangement.Rule.Edit"},
{ "F4", "RunToCursor", "EditSource"},
{ "F5", "Debugger.ResumeThread", "Resume", "UML.ApplyCurrentLayout"},
{ "alt DOWN", "NextOccurence", "ShowContent", "Arrangement.Rule.Match.Condition.Move.Down"},
{ "alt UP", "PreviousOccurence", "Arrangement.Rule.Match.Condition.Move.Up"},
{ "alt INSERT", "FileChooser.NewFolder", "Generate", "NewElement"},
{ "control 1", "ActivateProjectToolWindow", "DuplicatesForm.SendToLeft"},
{ "control 2", "ActivateProjectToolWindow", "FileChooser.GotoProject", "DuplicatesForm.SendToRight"},
{ "control 3", "ActivateProjectToolWindow", "FileChooser.GotoModule"},
{ "control BACK_SPACE", "EditorDeleteToWordStart", "ToggleDockMode"},
{ "control DIVIDE", "CollapseRegionRecursively", "Images.Editor.ActualSize"},
{ "control D", "EditorDuplicate", "Diff.ShowDiff", "CompareTwoFiles", "SendEOF", "FileChooser.GotoDesktop"},
{ "control M", "Vcs.ShowMessageHistory", "Move"},
{ "control R", "RenameElement", "Console.TableResult.Reload", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{ "control SLASH", "CommentByLineComment", "Images.Editor.ActualSize"},
{ "control U", "EditorToggleCase", "CommanderSwapPanels"},
{ "control N", "NewElement", "FileChooser.NewFolder"},
{ "control R", "RenameElement", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"},
{ "control U", SECOND_STROKE, "CommanderSwapPanels"},
{ "control O", "GotoClass", "GotoChangedFile"},
{ "control PERIOD", "GotoNextError", "EditorChooseLookupItemDot"},
{ "control alt DOWN", "MethodDown", "Console.TableResult.NextPage"},
@@ -285,58 +256,52 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase {
{ "shift alt F9", "ChooseDebugConfiguration", "ValidateXml", "ValidateJsp"},
{ "shift alt D", "ToggleFloatingMode", "hg4idea.QFold"},
{ "shift control DOWN", "EditorDuplicate", "ResizeToolWindowDown", },
{ "shift control ENTER", "EditorCompleteStatement", "Console.Jpa.GenerateSql"},
{ "shift control F7", "HighlightUsagesInFile", "XDebugger.NewWatch"},
{ "shift control UP", "EditorDuplicate", "ResizeToolWindowUp", },
{ "shift control alt P", "Print", "Graph.Print"},
{ "shift control K", "HippieCompletion", "Vcs.Push"},
{ "control alt E", "Console.History.Browse", "ExecuteInPyConsoleAction", "PerforceDirect.Edit"},
{ "TAB", "NextTemplateVariable", "NextParameter", "EditorTab", "EditorChooseLookupItemReplace", "EditorIndentSelection", "NextTemplateParameter", "ExpandLiveTemplateByTab"},
{ "shift TAB", "EditorUnindentSelection", "PreviousTemplateVariable", "PrevParameter", "PrevTemplateParameter"},
});
put("JBuilder", new String[][] {
{ "F2", "EditorTab", "GuiDesigner.EditComponent", "GuiDesigner.EditGroup", "Console.TableResult.EditValue", "XDebugger.SetValue", "XDebugger.EditWatch", "Arrangement.Rule.Edit"},
{ "F2", "EditorTab", "GuiDesigner.EditComponent", "GuiDesigner.EditGroup", "Console.TableResult.EditValue", "XDebugger.SetValue", "Arrangement.Rule.Edit"},
{ "F5", "ToggleBreakpointEnabled", "UML.ApplyCurrentLayout"},
{ "TAB", "EditorChooseLookupItemReplace", "NextTemplateVariable", "NextParameter", "EditorIndentSelection", "EmacsStyleIndent", "NextTemplateParameter", "ExpandLiveTemplateByTab"},
{ "control F6", "PreviousEditorTab", "PreviousTab", },
{ "control L", "Vcs.Log.FocusTextFilter", "EditorSelectLine"},
{ "control M", "Vcs.ShowMessageHistory", "OverrideMethods", },
{ "control N", "FileChooser.NewFolder", "GotoClass", "GotoChangedFile"},
{ "control P", "FileChooser.TogglePathShowing", "FindInPath"},
{ "shift control A", "SaveAll", "GotoAction"},
{ "shift control E", "RecentChangedFiles", "ExtractMethod"},
{ "shift control ENTER", "FindUsages", "Console.Jpa.GenerateSql"},
{ "shift control F6", "NextTab", "ChangeTypeSignature"},
{ "shift control G", "GotoSymbol", "ClassTemplateNavigation", "GoToClass"},
{ "control SUBTRACT", "CollapseAll", "CollapseRegion"},
{ "control alt I", "DatabaseView.GenerateScriptIntoConsole", "AutoIndentLines"},
{ "shift control X", "EditorToggleShowWhitespaces", "com.jetbrains.php.framework.FrameworkRunConsoleAction"},
});
put("Eclipse (Mac OS X)", new String[][] {
{ "meta BACK_SPACE", "EditorDeleteToWordStart", "$Delete"},
{ "F2", "Console.TableResult.EditValue", "QuickJavaDoc", "XDebugger.SetValue", "XDebugger.EditWatch", "Arrangement.Rule.Edit"},
{ "F3", "GotoDeclaration", "EditSource"},
{ "F5", "StepInto", "Console.TableResult.Reload", "UML.ApplyCurrentLayout"},
{ "alt DOWN", "MoveStatementDown", "Arrangement.Rule.Match.Condition.Move.Down"},
{ "alt UP", "MoveStatementUp", "Arrangement.Rule.Match.Condition.Move.Up"},
{ "control PERIOD", "EditorChooseLookupItemDot", "HippieCompletion"},
{ "meta 1", "FileChooser.GotoHome", "ShowIntentionActions", "DuplicatesForm.SendToLeft"},
{ "meta 3", "FileChooser.GotoModule", "GotoAction"},
{ "meta D", "EditorDeleteLine", "Diff.ShowDiff", "CompareTwoFiles", "SendEOF", "FileChooser.GotoDesktop"},
{ "meta I", "DatabaseView.PropertiesAction", "AutoIndentLines"},
{ "meta P", "FileChooser.TogglePathShowing", "Print"},
{ "meta R", "org.jetbrains.plugins.ruby.rails.console.ReloadSources", "RunToCursor"},
{ "meta U", "CommanderSwapPanels", "EvaluateExpression"},
{ "meta W", "CloseContent", "CloseActiveTab"},
{ "shift meta T", "GotoClass", "GotoChangedFile"},
{ "meta alt DOWN", "Console.TableResult.NextPage", "EditorDuplicateLines"},
{ "shift meta F11", "Run", "FocusTracer"},
{ "shift meta G", "ClassTemplateNavigation", "GoToClass", "FindUsages"},
{ "shift meta K", "Vcs.Push", "FindPrevious"},
{ "shift meta X", "EditorToggleCase", "com.jetbrains.php.framework.FrameworkRunConsoleAction"},
{ "shift meta U", "FindUsagesInFile", "ShelveChanges.UnshelveWithDialog"},
{ "control shift alt Z", "Vcs.RollbackChangedLines", "ChangesView.Revert"},
{ "meta alt I", "Inline", "DatabaseView.GenerateScriptIntoConsole"}
{ "meta BACK_SPACE", "EditorDeleteToWordStart", "$Delete"},
{ "F2", "Console.TableResult.EditValue", "QuickJavaDoc", "XDebugger.SetValue", "Arrangement.Rule.Edit"},
{ "F3", "GotoDeclaration", "EditSource"},
{ "F5", "StepInto", "UML.ApplyCurrentLayout"},
{ "alt DOWN", "MoveStatementDown", "Arrangement.Rule.Match.Condition.Move.Down"},
{ "alt UP", "MoveStatementUp", "Arrangement.Rule.Match.Condition.Move.Up"},
{ "control PERIOD", "EditorChooseLookupItemDot", "HippieCompletion"},
{ "meta 1", "FileChooser.GotoHome", "ShowIntentionActions", "DuplicatesForm.SendToLeft"},
{ "meta 3", "FileChooser.GotoModule", "GotoAction"},
{ "meta D", "EditorDeleteLine", "Diff.ShowDiff", "CompareTwoFiles", "SendEOF", "FileChooser.GotoDesktop"},
{ "meta I", "DatabaseView.PropertiesAction", "AutoIndentLines"},
{ "meta P", "FileChooser.TogglePathShowing", "Print"},
{ "meta R", "org.jetbrains.plugins.ruby.rails.console.ReloadSources", "RunToCursor"},
{ "meta U", "CommanderSwapPanels", "EvaluateExpression"},
{ "meta W", "CloseContent", "CloseActiveTab"},
{ "shift meta T", "GotoClass", "GotoChangedFile"},
{ "meta alt DOWN", "Console.TableResult.NextPage", "EditorDuplicateLines"},
{ "shift meta F11", "Run", "FocusTracer"},
{ "shift meta G", "ClassTemplateNavigation", "GoToClass", "FindUsages"},
{ "shift meta K", "Vcs.Push", "FindPrevious"},
{ "shift meta X", "EditorToggleCase", "com.jetbrains.php.framework.FrameworkRunConsoleAction"},
{ "shift meta U", "FindUsagesInFile", "ShelveChanges.UnshelveWithDialog"},
{ "control shift alt Z", "Vcs.RollbackChangedLines", "ChangesView.Revert"},
{ "meta alt I", "Inline", "DatabaseView.GenerateScriptIntoConsole"}
});
}};
// @formatter:on
@@ -15,26 +15,21 @@
*/
package com.intellij.keymap;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.KeyboardShortcut;
import com.intellij.openapi.actionSystem.Shortcut;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
import com.intellij.openapi.keymap.impl.KeymapImpl;
import com.intellij.openapi.keymap.impl.MacOSDefaultKeymap;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.testFramework.PlatformTestCase;
import com.intellij.ui.KeyStrokeAdapter;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashSet;
import junit.framework.TestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.InputEvent;
@@ -44,9 +39,8 @@ import java.util.*;
import static com.intellij.testFramework.assertions.Assertions.assertThat;
public abstract class KeymapsTestCaseBase extends PlatformTestCase {
private static final boolean OUTPUT_TEST_DATA = false;
private static final Set<String> LINUX_KEYMAPS = ContainerUtil.newHashSet("Default for XWin", "Default for GNOME", "Default for KDE");
protected static final String SECOND_STROKE = "SECOND_STROKE_SHORTCUT";
/**
@@ -80,177 +74,67 @@ public abstract class KeymapsTestCaseBase extends PlatformTestCase {
return result;
}
/**
* Drops records from baseDuplicates for shortcuts, that are registered in non-included plugin.xml
*/
protected static Map<String, Map<String, List<String>>> parseKnownDuplicates(Map<String, String[][]> baseDuplicates,
Map<String, String[][]> ideDuplicates) {
Map<String, Map<String, List<String>>> baseMap = parseKnownDuplicates(baseDuplicates);
baseMap.keySet().retainAll(ContainerUtil.map(KeymapManagerEx.getInstanceEx().getAllKeymaps(), Keymap::getName));
Map<String, Map<String, List<String>>> baseMapping = parseKnownDuplicates(baseDuplicates);
Map<String, Map<String, List<String>>> ideMapping = parseKnownDuplicates(ideDuplicates);
Map<String, Map<String, List<String>>> ideMap = parseKnownDuplicates(ideDuplicates);
HashMap<String, Map<String, List<String>>> result = new HashMap<>();
Map<String, Map<String, List<String>>> result = new HashMap<>();
for (String keymap : ContainerUtil.union(baseMap.keySet(), ideMap.keySet())) {
Map<String, List<String>> mapping = new HashMap<>();
result.put(keymap, mapping);
KeymapManagerEx km = KeymapManagerEx.getInstanceEx();
List<String> availableKeymaps = ContainerUtil.map(km.getAllKeymaps(), Keymap::getName);
Map<String, List<String>> baseMapping = baseMap.get(keymap);
Map<String, List<String>> ideMapping = ideMap.get(keymap);
for (String keymapName : availableKeymaps) {
Map<String, List<String>> map = result.computeIfAbsent(keymapName, key -> new HashMap<>());
Map<String, List<String>> baseMap = baseMapping.get(keymapName);
if (baseMap == null) continue;
if (baseMapping != null) mapping.putAll(baseMapping);
if (ideMapping != null) mapping.putAll(ideMapping);
Keymap keymap = km.getKeymap(keymapName);
for (String shortcut : baseMap.keySet()) {
Shortcut sc = parseShortcut(shortcut);
List<String> actionIds = ContainerUtil.filter(baseMap.get(shortcut), actionId -> {
return ActionManager.getInstance().getAction(actionId) != null ||
keymap.getShortcuts(actionId).length > 0;
});
if (actionIds.size() >= 2) {
map.put(shortcut, actionIds);
}
}
}
for (String keymap : ideMapping.keySet()) {
Map<String, List<String>> map = result.computeIfAbsent(keymap, key -> new HashMap<>());
Map<String, List<String>> ideMap = ideMapping.get(keymap);
map.putAll(ideMap);
}
return result;
}
public void testDuplicateShortcuts() {
public void testUnknownActionIds() {
StringBuilder failMessage = new StringBuilder();
Map<String, Map<String, List<String>>> knownDuplicates = getKnownDuplicates();
for (Keymap keymap : KeymapManagerEx.getInstanceEx().getSchemeManager().getAllSchemes()) {
String failure = checkDuplicatesInKeymap((KeymapImpl)keymap, knownDuplicates);
if (failMessage.length() > 0) {
failMessage.append("\n");
}
failMessage.append(failure);
}
if (failMessage.length() > 0) {
TestCase.fail(failMessage +
"\n" +
"Please specify 'use-shortcut-of' attribute for your action if it is similar to another action (but it won't appear in Settings/Keymap),\n" +
"reassign shortcut or, if absolutely must, modify the 'known duplicates list'");
}
}
@NotNull
@SuppressWarnings({"HardCodedStringLiteral"})
private static String checkDuplicatesInKeymap(@NotNull KeymapImpl keymap,
@NotNull Map<String, Map<String, List<String>>> allKnownDuplicates) {
Set<String> aids = keymap.getActionIdList();
removeBoundActionIds(aids);
Set<Shortcut> shortcuts = new THashSet<>();
nextId:
for (String id : aids) {
Map<String, List<String>> knownDuplicates = allKnownDuplicates.get(keymap.getName());
if (knownDuplicates != null) {
for (List<String> actionsMapping : knownDuplicates.values()) {
if (actionsMapping.contains(id)) {
continue nextId;
}
}
}
for (Shortcut shortcut : keymap.getShortcuts(id)) {
if (shortcut instanceof KeyboardShortcut) {
shortcuts.add(shortcut);
}
}
}
List<Shortcut> sorted = new ArrayList<>(shortcuts);
Collections.sort(sorted, Comparator.comparing(KeymapsTestCaseBase::getText));
if (OUTPUT_TEST_DATA) {
System.out.println("put(\"" + keymap.getName() + "\", new String[][] {");
}
else {
System.out.println(keymap.getName());
}
StringBuilder failMessage = new StringBuilder();
for (Shortcut shortcut : sorted) {
if (!(shortcut instanceof KeyboardShortcut)) {
continue;
}
Set<String> ids = new THashSet<>(Arrays.asList(keymap.getActionIds(shortcut)));
removeBoundActionIds(ids);
if (ids.size() == 1) {
continue;
}
Keymap parent = keymap.getParent();
if (parent != null) {
// ignore duplicates from default keymap
boolean differFromParent = false;
for (String id : ids) {
Shortcut[] here = keymap.getShortcuts(id);
Shortcut[] there = parent.getShortcuts(id);
if (keymap.getName().startsWith("Mac")) {
convertMac(there);
}
if (!new THashSet<>(Arrays.asList(here)).equals(new THashSet<>(Arrays.asList(there)))) {
differFromParent = true;
break;
}
}
if (!differFromParent) continue;
}
String def = "{ "
+ "\"" + getText(shortcut) + "\","
+ StringUtil.repeatSymbol(' ', 25 - getText(shortcut).length())
+ StringUtil.join(ids, StringUtil.QUOTER, ", ")
+ "},";
if (OUTPUT_TEST_DATA) {
System.out.println(def);
}
else {
if (failMessage.length() == 0) {
failMessage.append("Shortcut '").append(getText(shortcut)).append("' conflicts found in keymap '")
.append(keymap.getName()).append("':\n");
}
failMessage.append(def).append("\n");
}
}
if (OUTPUT_TEST_DATA) {
System.out.println("});");
}
return failMessage.toString();
}
private static void removeBoundActionIds(@NotNull Set<String> aids) {
KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
// explicitly bound to another action
for (Iterator<String> it = aids.iterator(); it.hasNext(); ) {
String id = it.next();
String sourceId = keymapManager.getActionBinding(id);
if (sourceId != null) {
it.remove();
}
}
}
public void testValidActionIds() {
Set<String> unknownActions = getUnknownActions();
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
Map<String, List<String>> missingActions = new FactoryMap<String, List<String>>() {
@Override
protected Map<String, List<String>> createMap() {
return new LinkedHashMap<>();
}
MultiMap<String, String> missingActions = new MultiMap<>();
@Nullable
@Override
protected List<String> create(String key) {
return new ArrayList<>();
}
};
for (Keymap keymap : KeymapManagerEx.getInstanceEx().getAllKeymaps()) {
List<String> ids = new ArrayList<>(keymap.getActionIdList());
ids.sort(null);
assertThat(ids).isEqualTo(new ArrayList<>(new LinkedHashSet<>(ids)));
Collection<String> ids = keymap.getActionIdList();
assertSameElements(ids, new HashSet<>(ids));
for (String cid : ids) {
if (unknownActions.contains(cid)) continue;
AnAction action = ActionManager.getInstance().getAction(cid);
if (action == null) {
if (OUTPUT_TEST_DATA) {
System.out.print("\"" + cid + "\", ");
}
else {
missingActions.get(keymap.getName()).add(cid);
}
missingActions.putValue(keymap.getName(), cid);
}
}
}
@@ -263,103 +147,214 @@ public abstract class KeymapsTestCaseBase extends PlatformTestCase {
}
}
if (!missingActions.isEmpty() || !reappearedAction.isEmpty()) {
StringBuilder message = new StringBuilder();
if (!missingActions.isEmpty()) {
for (Map.Entry<String, List<String>> keymapAndActions : missingActions.entrySet()) {
message.append("Unknown actions in keymap ").append(keymapAndActions.getKey()).append(", add them to unknown actions list:\n");
for (String action : keymapAndActions.getValue()) {
message.append("\"").append(action).append("\",").append("\n");
}
if (!missingActions.isEmpty()) {
for (String keymap : missingActions.keySet()) {
failMessage.append("Unknown actions in keymap ").append(keymap).append(", add them to unknown actions list:\n");
for (String action : missingActions.get(keymap)) {
failMessage.append("\"").append(action).append("\",").append("\n");
}
}
if (!reappearedAction.isEmpty()) {
message.append("The following actions have reappeared, remove them from unknown action list:\n");
for (String action : reappearedAction) {
message.append(action).append("\n");
}
}
if (!reappearedAction.isEmpty()) {
failMessage.append("The following actions have reappeared, remove them from unknown action list:\n");
for (String action : reappearedAction) {
failMessage.append(action).append("\n");
}
fail("\n" + message);
}
if (failMessage.length() > 0) {
fail("\n" + failMessage);
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
public void testIdsListIsConsistent() {
Map<String, Map<String, List<String>>> duplicates = getKnownDuplicates();
Set<String> allMaps = new THashSet<>(ContainerUtil.map(KeymapManagerEx.getInstanceEx().getAllKeymaps(), keymap -> keymap.getName()));
assertThat(ContainerUtil.subtract(allMaps, duplicates.keySet()))
.overridingErrorMessage("Modify 'known duplicates list' test data. Keymaps were added: %s",
ContainerUtil.subtract(allMaps, duplicates.keySet()))
public void testDuplicateShortcuts() {
StringBuilder failMessage = new StringBuilder();
Map<String, Map<Shortcut, List<String>>> expectedDuplicates = collectExpectedDuplicatedShortcuts();
Map<String, Map<Shortcut, List<String>>> actualDuplicates = collectActualDuplicatedShortcuts();
Set<String> allKeymaps = ContainerUtil.union(expectedDuplicates.keySet(), actualDuplicates.keySet());
Collection<String> newKeymaps = ContainerUtil.subtract(allKeymaps, expectedDuplicates.keySet());
Collection<String> missingKeymaps = ContainerUtil.subtract(allKeymaps, actualDuplicates.keySet());
assertThat(newKeymaps)
.overridingErrorMessage("Modify 'known duplicates list' test data. Keymaps were added: %s", newKeymaps)
.isEmpty();
assertThat(newKeymaps)
.overridingErrorMessage("Modify 'known duplicates list' test data. Keymaps were removed: %s", missingKeymaps)
.isEmpty();
assertThat(ContainerUtil.subtract(duplicates.keySet(), allMaps))
.overridingErrorMessage("Modify 'known duplicates list' test data. Keymaps were removed: %s",
ContainerUtil.subtract(duplicates.keySet(), allMaps))
.isEmpty();
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
MultiMap<Keymap, Shortcut> reassignedShortcuts = MultiMap.createLinked();
for (String name : duplicates.keySet()) {
Keymap keymap = KeymapManagerEx.getInstanceEx().getKeymap(name);
assertThat(keymap).overridingErrorMessage("KeyMap %s not found", name).isNotNull();
Map<String, List<String>> duplicateIdsList = duplicates.get(name);
Set<String> mentionedShortcuts = new THashSet<>();
for (Map.Entry<String, List<String>> shortcutMappings : duplicateIdsList.entrySet()) {
for (String keymap : ContainerUtil.sorted(allKeymaps)) {
Map<Shortcut, List<String>> actual = actualDuplicates.get(keymap);
Map<Shortcut, List<String>> expected = expectedDuplicates.get(keymap);
String shortcutString = shortcutMappings.getKey();
if (!mentionedShortcuts.add(shortcutString)) {
TestCase.fail("Shortcut '" + shortcutString + "' duplicate in keymap '" + keymap + "'. Please modify 'known duplicates list'");
}
Shortcut shortcut = parse(shortcutString);
Set<String> actualShortcuts = new THashSet<>(Arrays.asList(keymap.getActionIds(shortcut)));
StringBuilder keymapFailure = new StringBuilder();
for (Shortcut shortcut : ContainerUtil.union(actual.keySet(), expected.keySet())) {
List<String> expectedActions = expected.get(shortcut);
List<String> actualActions = actual.get(shortcut);
if (expectedActions == null || actualActions == null || !Comparing.haveEqualElements(expectedActions, actualActions)) {
String key = getText(shortcut);
removeBoundActionIds(actualShortcuts);
if (actualActions == null) {
keymapFailure.append(" ").append(key).append(" - <empty>\n");
}
else {
int keyLength = 24;
Set<String> expectedSc = new THashSet<>(shortcutMappings.getValue());
for (String s : actualShortcuts) {
if (!expectedSc.contains(s)) {
reassignedShortcuts.putValue(keymap, shortcut);
}
}
for (String s : expectedSc) {
if (!actualShortcuts.contains(s)) {
System.out.println("Expected action '" + s + "' does not reassign shortcut " + getText(shortcut) + " in keymap " + keymap + " or is not registered");
keymapFailure.append(" { ");
keymapFailure.append("\"").append(key).append("\"").append(", ");
keymapFailure.append(StringUtil.repeat(" ", Math.max(0, keyLength - key.length())));
List<String> values = ContainerUtil.map(ContainerUtil.sorted(actualActions), it -> "\"" + it + "\"");
keymapFailure.append(StringUtil.join(ContainerUtil.sorted(values), ", "));
keymapFailure.append("},\n");
}
}
}
if (keymapFailure.length() > 0) {
failMessage.append(String.format("Shortcut conflicts found in keymap '%s':\n", keymap));
failMessage.append(keymapFailure);
failMessage.append("\n");
}
}
if (!reassignedShortcuts.isEmpty()) {
StringBuilder message = new StringBuilder();
for (Map.Entry<Keymap, Collection<Shortcut>> keymapToShortcuts : reassignedShortcuts.entrySet()) {
Keymap keymap = keymapToShortcuts.getKey();
message
.append("The following shortcuts was reassigned in keymap ").append(keymap.getName())
.append(". Please modify known duplicates list:\n");
for (Shortcut eachShortcut : keymapToShortcuts.getValue()) {
message.append(" { ").append(StringUtil.wrapWithDoubleQuote(getText(eachShortcut))).append(",\t")
.append(StringUtil.join(keymap.getActionIds(eachShortcut), s -> StringUtil.wrapWithDoubleQuote(s), ", "))
.append("},\n");
}
}
TestCase.fail("\n" + message.toString());
if (failMessage.length() > 0) {
TestCase.fail(failMessage +
"\n" +
"Please specify 'use-shortcut-of' attribute for your action if it is similar to another action (but it won't appear in Settings/Keymap),\n" +
"reassign shortcut or, if absolutely must, modify the 'known duplicates list'");
}
}
private static Shortcut parse(String s) {
String[] sc = s.split(",");
KeyStroke fst = ActionManagerEx.getKeyStroke(sc[0]);
assert fst != null : s;
KeyStroke snd = null;
if (sc.length == 2) {
snd = ActionManagerEx.getKeyStroke(sc[1]);
@NotNull
private Map<String, Map<Shortcut, List<String>>> collectExpectedDuplicatedShortcuts() {
Map<String, Map<String, List<String>>> knownDuplicates = getKnownDuplicates();
Map<String, Map<Shortcut, List<String>>> expectedDuplicates = new HashMap<>();
for (String keymap : knownDuplicates.keySet()) {
Map<Shortcut, List<String>> keyDuplicates = new HashMap<>();
expectedDuplicates.put(keymap, keyDuplicates);
Map<String, List<String>> duplicates = knownDuplicates.get(keymap);
for (String shortcut : duplicates.keySet()) {
Shortcut keyboardShortcut = parseShortcut(shortcut);
List<String> actions = duplicates.computeIfAbsent(shortcut, key -> new ArrayList<>());
keyDuplicates.put(keyboardShortcut, actions);
}
}
return expectedDuplicates;
}
@NotNull
private static Map<String, Map<Shortcut, List<String>>> collectActualDuplicatedShortcuts() {
Map<String, Map<Shortcut, List<String>>> result = new HashMap<>();
KeymapManagerEx km = KeymapManagerEx.getInstanceEx();
Set<String> boundActions = km.getBoundActions();
Keymap[] keymaps = km.getAllKeymaps();
// fill shortcuts
for (Keymap keymap : keymaps) {
Map<Shortcut, List<String>> map = new HashMap<>();
result.put(keymap.getName(), map);
for (String actionId : keymap.getActionIds()) {
if (boundActions.contains(actionId)) continue;
for (Shortcut shortcut : keymap.getShortcuts(actionId)) {
List<String> actionList = map.computeIfAbsent(shortcut, key -> new ArrayList<>());
actionList.add(actionId);
if (shortcut instanceof KeyboardShortcut && ((KeyboardShortcut)shortcut).getSecondKeyStroke() != null) {
KeyboardShortcut firstStroke = new KeyboardShortcut(((KeyboardShortcut)shortcut).getFirstKeyStroke(), null);
List<String> firstStrokeActionList = map.computeIfAbsent(firstStroke, key -> new ArrayList<>());
if (!firstStrokeActionList.contains(SECOND_STROKE)) {
firstStrokeActionList.add(SECOND_STROKE);
}
}
}
}
}
// remove shortcuts, reused from parent keymap
for (Keymap keymap : keymaps) {
Map<Shortcut, List<String>> map = result.get(keymap.getName());
List<Shortcut> reusedShortcuts = new ArrayList<>();
for (Shortcut key : map.keySet()) {
Shortcut parentKey = convertShortcutForParent(key, keymap);
Keymap parent = keymap.getParent();
while (parent != null) {
Map<Shortcut, List<String>> parentMap = result.get(parent.getName());
List<String> shortcut = map.get(key);
List<String> parentShortcut = parentMap.get(parentKey);
if (parentShortcut != null && parentShortcut.containsAll(shortcut)) {
reusedShortcuts.add(key);
break;
}
parentKey = convertShortcutForParent(parentKey, parent);
parent = parent.getParent();
}
}
for (Shortcut shortcut : reusedShortcuts) {
map.remove(shortcut);
}
}
// remove non-duplicated shortcuts
for (Keymap keymap : keymaps) {
Map<Shortcut, List<String>> map = result.get(keymap.getName());
List<Shortcut> nonDuplicates = new ArrayList<>();
for (Map.Entry<Shortcut, List<String>> entry : map.entrySet()) {
if (entry.getValue().size() < 2) nonDuplicates.add(entry.getKey());
}
for (Shortcut key : nonDuplicates) {
map.remove(key);
}
}
return result;
}
@NotNull
private static Shortcut parseShortcut(@NotNull String s) {
if (s.contains("button")) {
return KeymapUtil.parseMouseShortcut(s);
}
else {
String[] sc = s.split(",");
assert sc.length <= 2 : s;
KeyStroke fst = KeyStrokeAdapter.getKeyStroke(sc[0]);
assert fst != null : s;
KeyStroke snd = null;
if (sc.length == 2) {
snd = KeyStrokeAdapter.getKeyStroke(sc[1]);
}
return new KeyboardShortcut(fst, snd);
}
return new KeyboardShortcut(fst, snd);
}
@NotNull
private static String getText(@NotNull Shortcut shortcut) {
if (shortcut instanceof KeyboardShortcut) {
if (shortcut instanceof MouseShortcut) {
return KeymapUtil.getMouseShortcutString((MouseShortcut)shortcut);
}
else if (shortcut instanceof KeyboardShortcut) {
KeyStroke fst = ((KeyboardShortcut)shortcut).getFirstKeyStroke();
String s = getText(fst);
@@ -379,10 +374,11 @@ public abstract class KeymapsTestCaseBase extends PlatformTestCase {
return text.substring(0, offset) + text.substring(offset).toUpperCase(Locale.ENGLISH);
}
private static void convertMac(@NotNull Shortcut[] there) {
for (int i = 0; i < there.length; i++) {
there[i] = MacOSDefaultKeymap.convertShortcutFromParent(there[i]);
private static Shortcut convertShortcutForParent(Shortcut key, @NotNull Keymap keymap) {
if (keymap.getName().startsWith(KeymapManager.MAC_OS_X_KEYMAP)) {
return MacOSDefaultKeymap.convertShortcutFromParent(key);
}
return key;
}