From 9fffa74cf59c81382c995cdfe9ad1945e5c8bd57 Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Thu, 23 Mar 2017 20:36:39 +0300 Subject: [PATCH] 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 --- .../intellij/openapi/keymap/KeymapUtil.java | 43 ++ .../openapi/keymap/impl/KeymapImpl.kt | 47 +- .../openapi/keymap/impl/MacOSDefaultKeymap.kt | 5 +- .../com/intellij/keymap/KeymapsTestCase.java | 115 ++--- .../intellij/keymap/KeymapsTestCaseBase.java | 466 +++++++++--------- 5 files changed, 319 insertions(+), 357 deletions(-) diff --git a/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java b/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java index 752e6bffcf0b..f9d9a5897843 100644 --- a/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java @@ -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 MouseShortcut + */ + 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) { diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapImpl.kt b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapImpl.kt index 808eb5c044a3..ce698339c8af 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapImpl.kt @@ -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, shortcuts2: List 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) } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/MacOSDefaultKeymap.kt b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/MacOSDefaultKeymap.kt index 241ee3e093e9..0317a7725186 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/MacOSDefaultKeymap.kt +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/MacOSDefaultKeymap.kt @@ -30,7 +30,10 @@ class MacOSDefaultKeymap(dataHolder: SchemeDataHolder, 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 } } diff --git a/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java b/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java index 08e354e01323..63dfd847f1b5 100644 --- a/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java +++ b/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java @@ -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 diff --git a/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCaseBase.java b/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCaseBase.java index 4298504731d6..5c752d74bd10 100644 --- a/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCaseBase.java +++ b/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCaseBase.java @@ -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 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>> parseKnownDuplicates(Map baseDuplicates, Map ideDuplicates) { - Map>> baseMap = parseKnownDuplicates(baseDuplicates); - baseMap.keySet().retainAll(ContainerUtil.map(KeymapManagerEx.getInstanceEx().getAllKeymaps(), Keymap::getName)); + Map>> baseMapping = parseKnownDuplicates(baseDuplicates); + Map>> ideMapping = parseKnownDuplicates(ideDuplicates); - Map>> ideMap = parseKnownDuplicates(ideDuplicates); + HashMap>> result = new HashMap<>(); - Map>> result = new HashMap<>(); - for (String keymap : ContainerUtil.union(baseMap.keySet(), ideMap.keySet())) { - Map> mapping = new HashMap<>(); - result.put(keymap, mapping); + KeymapManagerEx km = KeymapManagerEx.getInstanceEx(); + List availableKeymaps = ContainerUtil.map(km.getAllKeymaps(), Keymap::getName); - Map> baseMapping = baseMap.get(keymap); - Map> ideMapping = ideMap.get(keymap); + for (String keymapName : availableKeymaps) { + Map> map = result.computeIfAbsent(keymapName, key -> new HashMap<>()); + Map> 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 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> map = result.computeIfAbsent(keymap, key -> new HashMap<>()); + Map> ideMap = ideMapping.get(keymap); + + map.putAll(ideMap); + } + return result; } - public void testDuplicateShortcuts() { + public void testUnknownActionIds() { StringBuilder failMessage = new StringBuilder(); - Map>> 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>> allKnownDuplicates) { - Set aids = keymap.getActionIdList(); - removeBoundActionIds(aids); - - Set shortcuts = new THashSet<>(); - - nextId: - for (String id : aids) { - Map> knownDuplicates = allKnownDuplicates.get(keymap.getName()); - if (knownDuplicates != null) { - for (List actionsMapping : knownDuplicates.values()) { - if (actionsMapping.contains(id)) { - continue nextId; - } - } - } - - for (Shortcut shortcut : keymap.getShortcuts(id)) { - if (shortcut instanceof KeyboardShortcut) { - shortcuts.add(shortcut); - } - } - } - List 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 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 aids) { - KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx(); - // explicitly bound to another action - for (Iterator it = aids.iterator(); it.hasNext(); ) { - String id = it.next(); - String sourceId = keymapManager.getActionBinding(id); - if (sourceId != null) { - it.remove(); - } - } - } - - public void testValidActionIds() { Set unknownActions = getUnknownActions(); - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - Map> missingActions = new FactoryMap>() { - @Override - protected Map> createMap() { - return new LinkedHashMap<>(); - } + MultiMap missingActions = new MultiMap<>(); - @Nullable - @Override - protected List create(String key) { - return new ArrayList<>(); - } - }; for (Keymap keymap : KeymapManagerEx.getInstanceEx().getAllKeymaps()) { - List ids = new ArrayList<>(keymap.getActionIdList()); - ids.sort(null); - assertThat(ids).isEqualTo(new ArrayList<>(new LinkedHashSet<>(ids))); + Collection 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> 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>> duplicates = getKnownDuplicates(); - Set 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>> expectedDuplicates = collectExpectedDuplicatedShortcuts(); + Map>> actualDuplicates = collectActualDuplicatedShortcuts(); + + + Set allKeymaps = ContainerUtil.union(expectedDuplicates.keySet(), actualDuplicates.keySet()); + Collection newKeymaps = ContainerUtil.subtract(allKeymaps, expectedDuplicates.keySet()); + Collection 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 reassignedShortcuts = MultiMap.createLinked(); - for (String name : duplicates.keySet()) { - Keymap keymap = KeymapManagerEx.getInstanceEx().getKeymap(name); - assertThat(keymap).overridingErrorMessage("KeyMap %s not found", name).isNotNull(); - Map> duplicateIdsList = duplicates.get(name); - Set mentionedShortcuts = new THashSet<>(); - for (Map.Entry> shortcutMappings : duplicateIdsList.entrySet()) { + for (String keymap : ContainerUtil.sorted(allKeymaps)) { + Map> actual = actualDuplicates.get(keymap); + Map> 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 actualShortcuts = new THashSet<>(Arrays.asList(keymap.getActionIds(shortcut))); + StringBuilder keymapFailure = new StringBuilder(); + for (Shortcut shortcut : ContainerUtil.union(actual.keySet(), expected.keySet())) { + List expectedActions = expected.get(shortcut); + List 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(" - \n"); + } + else { + int keyLength = 24; - Set 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 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> 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>> collectExpectedDuplicatedShortcuts() { + Map>> knownDuplicates = getKnownDuplicates(); + + Map>> expectedDuplicates = new HashMap<>(); + for (String keymap : knownDuplicates.keySet()) { + Map> keyDuplicates = new HashMap<>(); + expectedDuplicates.put(keymap, keyDuplicates); + + Map> duplicates = knownDuplicates.get(keymap); + for (String shortcut : duplicates.keySet()) { + Shortcut keyboardShortcut = parseShortcut(shortcut); + List actions = duplicates.computeIfAbsent(shortcut, key -> new ArrayList<>()); + keyDuplicates.put(keyboardShortcut, actions); + } + } + return expectedDuplicates; + } + + @NotNull + private static Map>> collectActualDuplicatedShortcuts() { + Map>> result = new HashMap<>(); + + KeymapManagerEx km = KeymapManagerEx.getInstanceEx(); + Set boundActions = km.getBoundActions(); + Keymap[] keymaps = km.getAllKeymaps(); + + // fill shortcuts + for (Keymap keymap : keymaps) { + Map> 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 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 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> map = result.get(keymap.getName()); + + List reusedShortcuts = new ArrayList<>(); + + for (Shortcut key : map.keySet()) { + Shortcut parentKey = convertShortcutForParent(key, keymap); + Keymap parent = keymap.getParent(); + + while (parent != null) { + Map> parentMap = result.get(parent.getName()); + + List shortcut = map.get(key); + List 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> map = result.get(keymap.getName()); + + List nonDuplicates = new ArrayList<>(); + for (Map.Entry> 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; }