diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/CtrlYActionChooser.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/CtrlYActionChooser.java new file mode 100644 index 000000000000..aecb1874ae03 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/CtrlYActionChooser.java @@ -0,0 +1,109 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.editor.actions; + +import com.intellij.application.options.schemes.SchemeNameGenerator; +import com.intellij.ide.IdeEventQueue; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.ActionUtil; +import com.intellij.openapi.keymap.KeyMapBundle; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.keymap.ex.KeymapManagerEx; +import com.intellij.openapi.ui.Messages; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +public class CtrlYActionChooser { + private static final String ASK_ABOUT_SHORTCUT = "ask.about.ctrl.y.shortcut"; + + @Nullable + private static Keymap getCurrentKeymap() { + KeymapManager keymapManager = KeymapManager.getInstance(); + return keymapManager == null ? null : keymapManager.getActiveKeymap(); + } + + @NotNull + private static Keymap getRootKeymap(@NotNull Keymap keymap) { + while (keymap.canModify()) { + Keymap parent = keymap.getParent(); + if (parent == null) { + break; + } + else { + keymap = parent; + } + } + return keymap; + } + + private static boolean isCtrlY(AWTEvent event) { + if (!(event instanceof KeyEvent)) return false; + KeyEvent keyEvent = (KeyEvent)event; + int modifiers = keyEvent.getModifiers(); + return (keyEvent.getKeyCode() == KeyEvent.VK_Y && + ((modifiers & InputEvent.CTRL_MASK) != 0) && + ((modifiers & InputEvent.SHIFT_MASK) == 0) && + ((modifiers & InputEvent.ALT_GRAPH_MASK) == 0) && + ((modifiers & InputEvent.ALT_MASK) == 0) && + ((modifiers & InputEvent.META_MASK) == 0)); + } + + private static void patchKeymap(@NotNull Keymap currentKeymap) { + KeymapManager keymapManager = KeymapManager.getInstance(); + assert keymapManager instanceof KeymapManagerEx; + Keymap[] allKeymaps = ((KeymapManagerEx)keymapManager).getAllKeymaps(); + String name = SchemeNameGenerator.getUniqueName(KeyMapBundle.message("keymap.with.patched.redo.name", + currentKeymap.getPresentableName()), + n -> ContainerUtil.exists(allKeymaps, t -> n.equals(t.getName()) || + n.equals(t.getPresentableName()))); + Keymap newKeymap = currentKeymap.deriveKeymap(name); + KeyboardShortcut shortcut = KeyboardShortcut.fromString("control Y"); + newKeymap.removeShortcut(IdeActions.ACTION_EDITOR_DELETE_LINE, shortcut); + newKeymap.addShortcut(IdeActions.ACTION_REDO, shortcut); + ((KeymapManagerEx)keymapManager).getSchemeManager().addScheme(newKeymap); + ((KeymapManagerEx)keymapManager).setActiveKeymap(newKeymap); + } + + private static void invokeRedo(DataContext dataContext) { + AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_REDO); + if (action != null) ActionUtil.invokeAction(action, dataContext, ActionPlaces.MAIN_MENU, null, null); + } + + public static boolean isCurrentShortcutOk(DataContext dataContext) { + if (!PropertiesComponent.getInstance().isValueSet(ASK_ABOUT_SHORTCUT)) return true; + + Keymap keymap = getCurrentKeymap(); + if (keymap == null) return true; + Keymap rootKeymap = getRootKeymap(keymap); + if (!KeymapManager.DEFAULT_IDEA_KEYMAP.equals(rootKeymap.getName())) return true; + + AWTEvent event = IdeEventQueue.getInstance().getTrueCurrentEvent(); + if (!isCtrlY(event)) return true; + + int savedCount = IdeEventQueue.getInstance().getEventCount(); + int choice = Messages.showDialog(KeyMapBundle.message("keymap.patch.dialog.message"), KeyMapBundle.message("keymap.patch.dialog.title"), + new String[]{ + KeyMapBundle.message("keymap.patch.dialog.redo.option"), + KeyMapBundle.message("keymap.patch.dialog.delete.line.option"), + KeyMapBundle.message("keymap.patch.dialog.cancel.option") + }, -1, Messages.getInformationIcon(), null); + IdeEventQueue.getInstance().setEventCount(savedCount); // keep data context valid after showing modal dialog + + if (choice < 0 || choice > 1) return false; + PropertiesComponent.getInstance().unsetValue(ASK_ABOUT_SHORTCUT); + if (choice == 1) return true; + patchKeymap(keymap); + invokeRedo(dataContext); + return false; + } + + public static void askAboutShortcut() { + PropertiesComponent.getInstance().setValue(ASK_ABOUT_SHORTCUT, true); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteLineAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteLineAction.java index c6d882eb81e9..3b80d165d96a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteLineAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteLineAction.java @@ -27,6 +27,8 @@ import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; @@ -37,6 +39,11 @@ public class DeleteLineAction extends TextComponentEditorAction { } private static class Handler extends EditorWriteActionHandler { + @Override + public void doExecute(@NotNull Editor editor, @Nullable Caret caret, DataContext dataContext) { + if (CtrlYActionChooser.isCurrentShortcutOk(dataContext)) super.doExecute(editor, caret, dataContext); + } + @Override public void executeWriteAction(final Editor editor, Caret caret, DataContext dataContext) { FeatureUsageTracker.getInstance().triggerFeatureUsed("editor.delete.line"); diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.kt b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.kt index 45612e7aed90..96245de6c7c1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.kt @@ -6,10 +6,12 @@ import com.intellij.configurationStore.SchemeDataHolder import com.intellij.ide.WelcomeWizardUtil import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ConfigImportHelper import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.RoamingType import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage +import com.intellij.openapi.editor.actions.CtrlYActionChooser import com.intellij.openapi.keymap.Keymap import com.intellij.openapi.keymap.KeymapManagerListener import com.intellij.openapi.keymap.ex.KeymapManagerEx @@ -65,6 +67,10 @@ class KeymapManagerImpl(defaultKeymap: DefaultKeymap, factory: SchemeManagerFact schemeManager.loadSchemes() ourKeymapManagerInitialized = true + + if (ConfigImportHelper.isFirstSession() && !ConfigImportHelper.isConfigImported()) { + CtrlYActionChooser.askAboutShortcut(); + } } private fun fireActiveKeymapChanged(newScheme: Keymap?) { diff --git a/platform/platform-resources-en/src/messages/KeyMapBundle.properties b/platform/platform-resources-en/src/messages/KeyMapBundle.properties index e7786afaaf46..352374af70b0 100644 --- a/platform/platform-resources-en/src/messages/KeyMapBundle.properties +++ b/platform/platform-resources-en/src/messages/KeyMapBundle.properties @@ -101,3 +101,10 @@ filter.first.stroke.input=First Stroke: filter.second.stroke.input=Second Stroke: filter.shortcut.action.text=Find Actions by Shortcut disable.mnemonic.in.controls.check.box=Disable mnemonics in controls +keymap.with.patched.redo.name={0} Proper Redo +keymap.patch.dialog.title=Map Action to Ctrl+Y +# suppress inspection "TrailingSpacesInProperty" +keymap.patch.dialog.message=Use Ctrl+Y to Redo or to Delete Line?\n\nThis behavior can be changed later in Settings | Keymap.\n +keymap.patch.dialog.redo.option=Redo +keymap.patch.dialog.delete.line.option=Delete Line +keymap.patch.dialog.cancel.option=Cancel