diff --git a/.editorconfig b/.editorconfig
index 11a2a3b48..f5bb74985 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -3,3 +3,7 @@ root = true
 [*.java]
 indent_size = 2
 indent_style = space
+
+[*.kt]
+indent_size = 2
+indent_style = space
diff --git a/AUTHORS.md b/AUTHORS.md
index 8e89cb989..80e331559 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -66,6 +66,10 @@ Contributors:
 * [gecko655](mailto:aqwsedrft1234@yahoo.co.jp)
 * [Daniele Megna](mailto:megna.dany@gmail.com)
 * [Andrew Potter](mailto:apottere@gmail.com)
+* [Romain Gautier](mailto:romain.gautier@nimamoh.net)
+* [Elliot Courant](mailto:elliot.courant@wheniwork.com)
+* [Simon Rainer](mailto:simon.rainer@fau.de)
+* [Michael Ziwisky](mailto:mziwisky@instructure.com)
 
 If you are a contributor and your name is not listed here, feel free to
 contact the maintainer.
diff --git a/CHANGES.md b/CHANGES.md
index df20e630c..caddd64f5 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -38,6 +38,18 @@ To Be Released
 * [VIM-607](https://youtrack.jetbrains.com/issue/VIM-607) Fix memory leaks
 * [VIM-1546](https://youtrack.jetbrains.com/issue/VIM-1546) Storing TAB key as input
 * [VIM-1231](https://youtrack.jetbrains.com/issue/VIM-1231) Get indent from PsiFile
+* [VIM-1633](https://youtrack.jetbrains.com/issue/VIM-1633) Fixed sequential text object commands in visual mode
+* [VIM-1105](https://youtrack.jetbrains.com/issue/VIM-1105) Added the `:command` command
+* [VIM-1090](https://youtrack.jetbrains.com/issue/VIM-1090) Fixed tag motion with duplicate tags
+* [VIM-1644](https://youtrack.jetbrains.com/issue/VIM-1644) Fixed repeat with visual mode
+* Fixed invoking IDE actions instead of command line actions with same shortcuts
+* [VIM-1550](https://youtrack.jetbrains.com/issue/VIM-1550) Fixed leaving command line mode on backspace
+* Fix insert position of `<C-R>` in ex commands
+* Command line editing caret shape and insert digraph/register feedback
+* [VIM-1419](https://youtrack.jetbrains.com/issue/VIM-1419), [VIM-1493](https://youtrack.jetbrains.com/issue/VIM-1493) Correctly set focus when handling cmode mapping
+* Fix incorrect handling of subsequent key strokes after ex command line loses focus
+* [VIM-1240](https://youtrack.jetbrains.com/issue/VIM-1240) Improve UI of ex command line and output panel
+* [VIM-1485](https://youtrack.jetbrains.com/issue/VIM-1485) Remove incorrect gap between ex command line label and text
 
 0.51, 2019-02-12
 ----------------
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9074ae57e..7e3f291ae 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -66,7 +66,7 @@ in the issue tracker.
 
 ### Copyright
 
-1. Go to `Preferences | Appearance & Behavior | Scopes`, press "+" button, `local`.  
+1. Go to `Preferences | Appearance & Behavior | Scopes`, press "+" button, `Shared`.  
        Name: Copyright scope  
        Pattern: `file[IdeaVIM.main]:com//*||file[IdeaVIM.test]:*/`
 
diff --git a/index.txt b/index.txt
deleted file mode 100644
index af7097062..000000000
--- a/index.txt
+++ /dev/null
@@ -1,191 +0,0 @@
-*index.txt*
-
-
-                  IDEAVIM REFERENCE MANUAL       based on Vim Reference Manual
-
-                                                                *index*
-This file contains a list of commands that are covered with tests, for each
-mode, with a tag and a short description. The lists are sorted on ASCII value.
-
-Tip: When looking for certain functionality, use a search command.  E.g.,
-to look for deleting something, use: "/delete".
-
-1. Insert mode                          |insert-index|
-2. Normal mode                          |normal-index|
-   2.1. Text objects                    |objects|
-   2.3. Square bracket commands         |[|
-3. Visual mode                          |visual-index|
-5. EX commands                          |ex-cmd-index|
-
-==============================================================================
-1. Insert mode                                          *insert-index*
-
-tag             char            action in Insert mode   ~
------------------------------------------------------------------------
-|i_CTRL-K|      CTRL-K {char1} {char2}
-                                enter digraph
-|i_CTRL-O|      CTRL-O          execute a single command and return to insert
-                                mode
-|i_CTRL-R|      CTRL-R {0-9a-z"%#*:=}
-                                insert the contents of a register
-|i_CTRL-W|      CTRL-W          delete word before the cursor
-
-==============================================================================
-2. Normal mode                                          *normal-index*
-
-CHAR     any non-blank character
-WORD     a sequence of non-blank characters
-N        a number entered before the command
-{motion} a cursor movement command
-Nmove    the text that is moved over with a {motion}
-SECTION  a section that possibly starts with '}' instead of '{'
-
-note: 1 = cursor movement command; 2 = can be undone/redone
-
-tag             char          note action in Normal mode        ~
-------------------------------------------------------------------------------
-|quote|         "{a-zA-Z0-9.%#:-"}  use register {a-zA-Z0-9.%#:-"} for next
-                                   delete, yank or put (uppercase to append)
-                                   ({.%#:} only work with put)
-|%|             %               1  find the next (curly/square) bracket on
-                                   this line and go to its match, or go to
-                                   matching comment bracket, or go to matching
-                                   preprocessor directive.
-|/|             /{pattern}<CR>  1  search forward for the Nth occurrence of
-                                   {pattern}
-|count|         0               1  cursor to the first char of the line
-|count|         1                  prepend to command to give a count
-|count|         2                       "
-|count|         3                       "
-|count|         4                       "
-|count|         5                       "
-|count|         6                       "
-|count|         7                       "
-|count|         8                       "
-|count|         9                       "
-|F|             F{char}         1  cursor to the Nth occurrence of {char} to
-                                   the left
-|O|             O               2  begin a new line above the cursor and
-                                   insert text, repeat N times
-|P|             ["x]P           2  put the text [from buffer x] before the
-                                   cursor N times
-|T|             T{char}         1  cursor till after Nth occurrence of {char}
-                                   to the left
-|Y|             ["x]Y              yank N lines [into buffer x]; synonym for
-                                   "yy"
-|c|             ["x]c{motion}   2  delete Nmove text [into buffer x] and start
-                                   insert
-|cc|            ["x]cc          2  delete N lines [into buffer x] and start
-|d|             ["x]d{motion}   2  delete Nmove text [into buffer x]
-|f|             f{char}         1  cursor to Nth occurrence of {char} to the
-                                   right
-|i|             i               2  insert text before the cursor N times
-|p|             ["x]p           2  put the text [from register x] after the
-                                   cursor N times
-|q|             q{0-9a-zA-Z"}      record typed characters into named register
-                                   {0-9a-zA-Z"} (uppercase to append)
-|q|             q                  (while recording) stops recording
-|t|             t{char}         1  cursor till before Nth occurrence of {char}
-                                   to the right
-|y|             ["x]y{motion}      yank Nmove text [into buffer x]
-|yy|            ["x]yy             yank N lines [into buffer x]
-|~|             ~               2  'tildeop' off: switch case of N characters
-                                   under cursor and move the cursor N
-                                   characters to the right
-
-==============================================================================
-2.1 Text objects                                                *objects*
-
-These can be used after an operator or in Visual mode to select an object.
-
-tag             command            action in op-pending and Visual mode ~
-------------------------------------------------------------------------------
-|v_aquote|      a"                 double quoted string
-|v_a'|          a'                 single quoted string
-|v_a(|          a(                 same as ab
-|v_a)|          a)                 same as ab
-|v_a<|          a<                 "a <>" from '<' to the matching '>'
-|v_a>|          a>                 same as a<
-|v_aB|          aB                 "a Block" from "[{" to "]}" (with brackets)
-|v_aW|          aW                 "a WORD" (with white space)
-|v_a[|          a[                 "a []" from '[' to the matching ']'
-|v_a]|          a]                 same as a[
-|v_a`|          a`                 string in backticks
-|v_ab|          ab                 "a block" from "[(" to "])" (with braces)
-|v_ap|          ap                 "a paragraph" (with white space)
-|v_as|          as                 "a sentence" (with white space)
-|v_aw|          aw                 "a word" (with white space)
-|v_a{|          a{                 same as aB
-|v_a}|          a}                 same as aB
-|v_iquote|      i"                 double quoted string without the quotes
-|v_i'|          i'                 single quoted string without the quotes
-|v_i(|          i(                 same as ib
-|v_i)|          i)                 same as ib
-|v_i<|          i<                 "inner <>" from '<' to the matching '>'
-|v_i>|          i>                 same as i<
-|v_iB|          iB                 "inner Block" from "[{" and "]}"
-|v_iW|          iW                 "inner WORD"
-|v_i[|          i[                 "inner []" from '[' to the matching ']'
-|v_i]|          i]                 same as i[
-|v_i`|          i`                 string in backticks without the backticks
-|v_ib|          ib                 "inner block" from "[(" to "])"
-|v_ip|          ip                 "inner paragraph"
-|v_is|          is                 "inner sentence"
-|v_iw|          iw                 "inner word"
-|v_i{|          i{                 same as iB
-|v_i}|          i}                 same as iB
-
-==============================================================================
-2.3 Square bracket commands                                     *[* *]*
-
-tag             char          note action in Normal mode        ~
-------------------------------------------------------------------------------
-|[(|            [(              1  cursor N times back to unmatched '('
-|[{|            [{              1  cursor N times back to unmatched '{'
-|])|            ])              1  cursor N times forward to unmatched ')'
-|]}|            ]}              1  cursor N times forward to unmatched '}'
-
-==============================================================================
-2.4 Commands starting with 'g'                                          *g*
-
-tag             char          note action in Normal mode        ~
-------------------------------------------------------------------------------
-|gg|            gg              1  cursor to line N, default first line
-|gi|            gi              2  like "i", but first move to the |'^| mark
-
-==============================================================================
-3. Visual mode                                          *visual-index*
-
-Most commands in Visual mode are the same as in Normal mode.  The ones listed
-here are those that are different.
-
-tag             command       note action in Visual mode        ~
-------------------------------------------------------------------------------
-|v_y|           y                  yank the highlighted area
-
-==============================================================================
-4. Command-line editing                                 *ex-edit-index*
-
-Get to the command-line with the ':', '!', '/' or '?' commands.
-Normal characters are inserted at the current cursor position.
-"Completion" below refers to context-sensitive completion.  It will complete
-file names, tags, commands etc. as appropriate.
-
-tag             command       action in Command-line editing mode       ~
-------------------------------------------------------------------------------
-|c_CTRL-R|      CTRL-R {0-9a-z"%#*:= CTRL-F CTRL-P CTRL-W CTRL-A}
-                                insert the contents of a register or object
-                                under the cursor as if typed
-
-==============================================================================
-5. EX commands                                  *ex-cmd-index*
-
-This is a brief but complete listing of all the ":" commands, without
-mentioning any arguments.  The optional part of the command name is inside [].
-The commands are sorted on the non-optional part of their name.
-
-tag           command         action ~
-------------------------------------------------------------------------------
-|:display|      :di[splay]      display registers
-|:registers|    :reg[isters]    display the contents of registers
-|:substitute|   :s[ubstitute]   find and replace text
\ No newline at end of file
diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index b85019bb0..1290590a2 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -4,6 +4,7 @@
   <change-notes><![CDATA[
       <p>To be released:</p>
       <ul>
+        <li>Support :command command</li>
         <li>Support :shell command</li>
         <li>Support :tabnext and :tabprevious commands</li>
         <li>Commentary extension</li>
@@ -67,6 +68,9 @@
     <component>
       <implementation-class>com.maddyhome.idea.vim.VimPlugin</implementation-class>
     </component>
+    <component>
+      <implementation-class>com.maddyhome.idea.vim.VimLocalConfig</implementation-class>
+    </component>
   </application-components>
 
   <extensionPoints>
@@ -387,10 +391,7 @@
     <action id="VimPlaybackLastRegister" class="com.maddyhome.idea.vim.action.macro.PlaybackLastRegisterAction" text="Playback Last Register"/>
 
     <!-- Command Line -->
-    <action id="VimExBackspace" class="com.maddyhome.idea.vim.action.ex.BackspaceAction" text="Backspace"/>
     <action id="VimProcessExEntry" class="com.maddyhome.idea.vim.action.ex.ProcessExEntryAction" text="Process Ex Entry"/>
-    <action id="VimProcessExKey" class="com.maddyhome.idea.vim.action.ex.ProcessExKeyAction" text="Process Ex Key"/>
-    <action id="VimCancelExEntry" class="com.maddyhome.idea.vim.action.ex.CancelExEntryAction" text="Cancel Ex Entry"/>
 
     <!-- Other -->
     <action id="VimLastSearchReplace" class="com.maddyhome.idea.vim.action.change.change.ChangeLastSearchReplaceAction" text="Repeat Last :s"/>
@@ -403,7 +404,7 @@
     <action id="VimUndo" class="com.maddyhome.idea.vim.action.change.UndoAction" text="Undo"/>
 
     <!-- Internal -->
-    <action id="VimInternalAddInlays" class="com.maddyhome.idea.vim.action.internal.AddInlaysAction" text="Vim (internal) add test inlays" internal="true"/>
+    <action id="VimInternalAddInlays" class="com.maddyhome.idea.vim.action.internal.AddInlaysAction" text="Add test inlays | IdeaVim internal" internal="true"/>
 
     <!-- Keys -->
     <action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction" text="Shortcuts"/>
diff --git a/src/com/maddyhome/idea/vim/KeyHandler.java b/src/com/maddyhome/idea/vim/KeyHandler.java
index 53d48cf1b..22ae5104a 100644
--- a/src/com/maddyhome/idea/vim/KeyHandler.java
+++ b/src/com/maddyhome/idea/vim/KeyHandler.java
@@ -247,15 +247,15 @@ public class KeyHandler {
     final CommandState commandState = CommandState.getInstance(editor);
     commandState.stopMappingTimer();
 
-    final List<KeyStroke> mappingKeys = commandState.getMappingKeys();
-    final List<KeyStroke> fromKeys = new ArrayList<KeyStroke>(mappingKeys);
-    fromKeys.add(key);
-
     final MappingMode mappingMode = commandState.getMappingMode();
     if (MappingMode.NVO.contains(mappingMode) && (state != State.NEW_COMMAND || currentArg != Argument.Type.NONE)) {
       return false;
     }
 
+    final List<KeyStroke> mappingKeys = commandState.getMappingKeys();
+    final List<KeyStroke> fromKeys = new ArrayList<KeyStroke>(mappingKeys);
+    fromKeys.add(key);
+
     final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mappingMode);
     final MappingInfo currentMappingInfo = mapping.get(fromKeys);
     final MappingInfo prevMappingInfo = mapping.get(mappingKeys);
diff --git a/src/com/maddyhome/idea/vim/RegisterActions.java b/src/com/maddyhome/idea/vim/RegisterActions.java
index 4bcb23614..3c391cbec 100644
--- a/src/com/maddyhome/idea/vim/RegisterActions.java
+++ b/src/com/maddyhome/idea/vim/RegisterActions.java
@@ -31,11 +31,11 @@ import javax.swing.*;
 import java.awt.event.KeyEvent;
 import java.util.EnumSet;
 
-public class RegisterActions {
+class RegisterActions {
   /**
    * Register all the key/action mappings for the plugin.
    */
-  public static void registerActions() {
+  static void registerActions() {
     registerVimCommandActions();
 
     registerInsertModeActions();
diff --git a/src/com/maddyhome/idea/vim/VimLocalConfig.kt b/src/com/maddyhome/idea/vim/VimLocalConfig.kt
new file mode 100644
index 000000000..581684e19
--- /dev/null
+++ b/src/com/maddyhome/idea/vim/VimLocalConfig.kt
@@ -0,0 +1,38 @@
+package com.maddyhome.idea.vim
+
+import com.intellij.configurationStore.APP_CONFIG
+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.maddyhome.idea.vim.VimPlugin.STATE_VERSION
+import org.jdom.Element
+
+/**
+ * @author Alex Plate
+ */
+
+@State(name = "VimLocalSettings",
+        storages = [Storage("$APP_CONFIG$/vim_local_settings.xml", roamingType = RoamingType.DISABLED)])
+class VimLocalConfig : PersistentStateComponent<Element> {
+    override fun getState(): Element {
+        val element = Element("ideavim-local")
+
+        val state = Element("state")
+        state.setAttribute("version", Integer.toString(STATE_VERSION))
+        element.addContent(state)
+
+        VimPlugin.getMark().saveData(element)
+        VimPlugin.getRegister().saveData(element)
+        VimPlugin.getSearch().saveData(element)
+        VimPlugin.getHistory().saveData(element)
+        return element
+    }
+
+    override fun loadState(state: Element) {
+        VimPlugin.getMark().readData(state)
+        VimPlugin.getRegister().readData(state)
+        VimPlugin.getSearch().readData(state)
+        VimPlugin.getHistory().readData(state)
+    }
+}
\ No newline at end of file
diff --git a/src/com/maddyhome/idea/vim/VimPlugin.java b/src/com/maddyhome/idea/vim/VimPlugin.java
index a02ff6d4e..be2532b9b 100644
--- a/src/com/maddyhome/idea/vim/VimPlugin.java
+++ b/src/com/maddyhome/idea/vim/VimPlugin.java
@@ -84,7 +84,7 @@ import java.util.concurrent.TimeUnit;
  */
 @State(
   name = "VimSettings",
-  storages = {@Storage(file = "$APP_CONFIG$/vim_settings.xml")})
+  storages = {@Storage("$APP_CONFIG$/vim_settings.xml")})
 public class VimPlugin implements ApplicationComponent, PersistentStateComponent<Element> {
   private static final String IDEAVIM_COMPONENT_NAME = "VimPlugin";
   private static final String IDEAVIM_PLUGIN_ID = "IdeaVIM";
@@ -92,7 +92,9 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
   public static final String IDEAVIM_NOTIFICATION_ID = "ideavim";
   public static final String IDEAVIM_STICKY_NOTIFICATION_ID = "ideavim-sticky";
   public static final String IDEAVIM_NOTIFICATION_TITLE = "IdeaVim";
-  public static final int STATE_VERSION = 4;
+  public static final int STATE_VERSION = 5;
+
+  private static long lastBeepTimeMillis;
 
   private boolean error = false;
 
@@ -106,6 +108,7 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
 
   @NotNull private final MotionGroup motion;
   @NotNull private final ChangeGroup change;
+  @NotNull private final CommandGroup command;
   @NotNull private final MarkGroup mark;
   @NotNull private final RegisterGroup register;
   @NotNull private final FileGroup file;
@@ -124,6 +127,7 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
   public VimPlugin() {
     motion = new MotionGroup();
     change = new ChangeGroup();
+    command = new CommandGroup();
     mark = new MarkGroup();
     register = new RegisterGroup();
     file = new FileGroup();
@@ -201,10 +205,6 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
     state.setAttribute("enabled", Boolean.toString(enabled));
     element.addContent(state);
 
-    mark.saveData(element);
-    register.saveData(element);
-    search.saveData(element);
-    history.saveData(element);
     key.saveData(element);
     editor.saveData(element);
 
@@ -227,10 +227,13 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
       previousKeyMap = state.getAttributeValue("keymap");
     }
 
-    mark.readData(element);
-    register.readData(element);
-    search.readData(element);
-    history.readData(element);
+    if (previousStateVersion > 0 && previousStateVersion < 5) {
+      // Migrate settings from 4 to 5 version
+      mark.readData(element);
+      register.readData(element);
+      search.readData(element);
+      history.readData(element);
+    }
     key.readData(element);
     editor.readData(element);
   }
@@ -245,6 +248,9 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
     return getInstance().change;
   }
 
+  @NotNull
+  public static CommandGroup getCommand() { return getInstance().command; }
+
   @NotNull
   public static MarkGroup getMark() {
     return getInstance().mark;
@@ -359,7 +365,12 @@ public class VimPlugin implements ApplicationComponent, PersistentStateComponent
       getInstance().error = true;
     }
     else if (!Options.getInstance().isSet("visualbell")) {
-      Toolkit.getDefaultToolkit().beep();
+      // Vim only allows a beep once every half second - :help 'visualbell'
+      final long currentTimeMillis = System.currentTimeMillis();
+      if (currentTimeMillis - lastBeepTimeMillis > 500) {
+        Toolkit.getDefaultToolkit().beep();
+        lastBeepTimeMillis = currentTimeMillis;
+      }
     }
   }
 
diff --git a/src/com/maddyhome/idea/vim/VimTypedActionHandler.java b/src/com/maddyhome/idea/vim/VimTypedActionHandler.java
index 487bf6fe7..c7b11a6be 100644
--- a/src/com/maddyhome/idea/vim/VimTypedActionHandler.java
+++ b/src/com/maddyhome/idea/vim/VimTypedActionHandler.java
@@ -42,11 +42,9 @@ import java.awt.event.KeyEvent;
 public class VimTypedActionHandler implements TypedActionHandlerEx {
   private static final Logger logger = Logger.getInstance(VimTypedActionHandler.class.getName());
 
-  private final TypedActionHandler origHandler;
   @NotNull private final KeyHandler handler;
 
-  public VimTypedActionHandler(TypedActionHandler origHandler) {
-    this.origHandler = origHandler;
+  VimTypedActionHandler(TypedActionHandler origHandler) {
     handler = KeyHandler.getInstance();
     handler.setOriginalHandler(origHandler);
   }
@@ -57,7 +55,7 @@ public class VimTypedActionHandler implements TypedActionHandlerEx {
       handler.beforeHandleKey(editor, KeyStroke.getKeyStroke(charTyped), context, plan);
     }
     else {
-      TypedActionHandler originalHandler = KeyHandler.getInstance().getOriginalHandler();
+      TypedActionHandler originalHandler = handler.getOriginalHandler();
       if (originalHandler instanceof TypedActionHandlerEx) {
         ((TypedActionHandlerEx)originalHandler).beforeExecute(editor, charTyped, context, plan);
       }
@@ -76,6 +74,7 @@ public class VimTypedActionHandler implements TypedActionHandlerEx {
     }
     else {
       try (final VimListenerSuppressor ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
+        TypedActionHandler origHandler = handler.getOriginalHandler();
         origHandler.execute(editor, charTyped, context);
       }
     }
diff --git a/src/com/maddyhome/idea/vim/action/VimPluginToggleAction.java b/src/com/maddyhome/idea/vim/action/VimPluginToggleAction.java
index c72afb1cb..8f1d4b469 100644
--- a/src/com/maddyhome/idea/vim/action/VimPluginToggleAction.java
+++ b/src/com/maddyhome/idea/vim/action/VimPluginToggleAction.java
@@ -22,6 +22,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.ToggleAction;
 import com.intellij.openapi.project.DumbAware;
 import com.maddyhome.idea.vim.VimPlugin;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * This class is used to handle the Vim Plugin enabled/disabled toggle. This is most likely used as a menu option
@@ -34,7 +35,7 @@ public class VimPluginToggleAction extends ToggleAction implements DumbAware {
    * @param event The event that triggered the action
    * @return true if the toggle is on, false if off
    */
-  public boolean isSelected(AnActionEvent event) {
+  public boolean isSelected(@NotNull AnActionEvent event) {
     return VimPlugin.isEnabled();
   }
 
@@ -44,7 +45,7 @@ public class VimPluginToggleAction extends ToggleAction implements DumbAware {
    * @param event The event that triggered the action
    * @param b     The new state - true is on, false is off
    */
-  public void setSelected(AnActionEvent event, boolean b) {
+  public void setSelected(@NotNull AnActionEvent event, boolean b) {
     VimPlugin.setEnabled(b);
   }
 }
diff --git a/src/com/maddyhome/idea/vim/action/ex/ProcessExEntryAction.java b/src/com/maddyhome/idea/vim/action/ex/ProcessExEntryAction.java
index 9edb19add..205569796 100644
--- a/src/com/maddyhome/idea/vim/action/ex/ProcessExEntryAction.java
+++ b/src/com/maddyhome/idea/vim/action/ex/ProcessExEntryAction.java
@@ -27,6 +27,9 @@ import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
 import org.jetbrains.annotations.NotNull;
 
 /**
+ * Called by KeyHandler to process the contents of the ex entry panel
+ * <p>
+ * The mapping for this action means that the ex command is executed as a write action
  */
 public class ProcessExEntryAction extends EditorAction {
   public ProcessExEntryAction() {
diff --git a/src/com/maddyhome/idea/vim/action/ex/ProcessExKeyAction.java b/src/com/maddyhome/idea/vim/action/ex/ProcessExKeyAction.java
deleted file mode 100644
index a32844fdb..000000000
--- a/src/com/maddyhome/idea/vim/action/ex/ProcessExKeyAction.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
- * Copyright (C) 2003-2019 The IdeaVim authors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.maddyhome.idea.vim.action.ex;
-
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.actionSystem.EditorAction;
-import com.maddyhome.idea.vim.VimPlugin;
-import com.maddyhome.idea.vim.command.Command;
-import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
-import org.jetbrains.annotations.NotNull;
-
-/**
- */
-public class ProcessExKeyAction extends EditorAction {
-  public ProcessExKeyAction() {
-    super(new Handler());
-  }
-
-  private static class Handler extends EditorActionHandlerBase {
-    protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
-      return VimPlugin.getProcess().processExKey(editor, cmd.getKeys().get(0));
-    }
-  }
-}
diff --git a/src/com/maddyhome/idea/vim/action/window/WindowDownAction.java b/src/com/maddyhome/idea/vim/action/window/WindowDownAction.java
index 6b78c3146..16604e632 100644
--- a/src/com/maddyhome/idea/vim/action/window/WindowDownAction.java
+++ b/src/com/maddyhome/idea/vim/action/window/WindowDownAction.java
@@ -54,7 +54,7 @@ public class WindowDownAction extends VimCommandAction {
   @NotNull
   @Override
   public Set<List<KeyStroke>> getKeyStrokesSet() {
-    return parseKeysSet("<C-W>j", "<C-W><Down>");
+    return parseKeysSet("<C-W>j", "<C-W><C-J>", "<C-W><Down>");
   }
 
   @NotNull
diff --git a/src/com/maddyhome/idea/vim/action/window/WindowLeftAction.java b/src/com/maddyhome/idea/vim/action/window/WindowLeftAction.java
index 5cc13dd65..e7baf5eba 100644
--- a/src/com/maddyhome/idea/vim/action/window/WindowLeftAction.java
+++ b/src/com/maddyhome/idea/vim/action/window/WindowLeftAction.java
@@ -54,7 +54,7 @@ public class WindowLeftAction extends VimCommandAction {
   @NotNull
   @Override
   public Set<List<KeyStroke>> getKeyStrokesSet() {
-    return parseKeysSet("<C-W>h", "<C-W><Left>");
+    return parseKeysSet("<C-W>h", "<C-W><C-H>", "<C-W><Left>");
   }
 
   @NotNull
diff --git a/src/com/maddyhome/idea/vim/action/window/WindowRightAction.java b/src/com/maddyhome/idea/vim/action/window/WindowRightAction.java
index 0ffe4dc9b..8dbd1bbcb 100644
--- a/src/com/maddyhome/idea/vim/action/window/WindowRightAction.java
+++ b/src/com/maddyhome/idea/vim/action/window/WindowRightAction.java
@@ -54,7 +54,7 @@ public class WindowRightAction extends VimCommandAction {
   @NotNull
   @Override
   public Set<List<KeyStroke>> getKeyStrokesSet() {
-    return parseKeysSet("<C-W>l", "<C-W><Right>");
+    return parseKeysSet("<C-W>l", "<C-W><C-L>", "<C-W><Right>");
   }
 
   @NotNull
diff --git a/src/com/maddyhome/idea/vim/action/window/WindowUpAction.java b/src/com/maddyhome/idea/vim/action/window/WindowUpAction.java
index bbed5cdb1..b4f2b81b5 100644
--- a/src/com/maddyhome/idea/vim/action/window/WindowUpAction.java
+++ b/src/com/maddyhome/idea/vim/action/window/WindowUpAction.java
@@ -54,7 +54,7 @@ public class WindowUpAction extends VimCommandAction {
   @NotNull
   @Override
   public Set<List<KeyStroke>> getKeyStrokesSet() {
-    return parseKeysSet("<C-W>k", "<C-W><Up>");
+    return parseKeysSet("<C-W>k", "<C-W><C-K>", "<C-W><Up>");
   }
 
   @NotNull
diff --git a/src/com/maddyhome/idea/vim/common/Alias.kt b/src/com/maddyhome/idea/vim/common/Alias.kt
new file mode 100644
index 000000000..873a9cf12
--- /dev/null
+++ b/src/com/maddyhome/idea/vim/common/Alias.kt
@@ -0,0 +1,75 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.maddyhome.idea.vim.common
+
+import com.maddyhome.idea.vim.VimPlugin
+
+/**
+ * @author Elliot Courant
+ */
+class Alias(
+        private val minimumNumberOfArguments: Int,
+        private val maximumNumberOfArguments: Int,
+        val name: String,
+        val command: String
+) {
+    val numberOfArguments =
+            when {
+                this.minimumNumberOfArguments == 0 && this.maximumNumberOfArguments == 0 -> "0" // No arguments
+                this.minimumNumberOfArguments == 0 && this.maximumNumberOfArguments == -1 -> "*" // Any number of arguments
+                this.minimumNumberOfArguments == 0 && this.maximumNumberOfArguments == 1 -> "?" // Zero or one argument
+                this.minimumNumberOfArguments == 1 && this.maximumNumberOfArguments == -1 -> "+" // One or more arguments
+                else -> this.minimumNumberOfArguments.toString() // Specified number of arguments
+            }
+
+    private companion object {
+        const val LessThan = "<lt>"
+        const val Count = "<count>"
+        const val Arguments = "<args>"
+        const val QuotedArguments = "<q-args>"
+    }
+
+    fun getCommand(input: String, count: Int): String {
+        if (this.maximumNumberOfArguments == 0 && this.maximumNumberOfArguments == 0) {
+            return this.command
+        }
+        var compiledCommand = this.command
+        val cleanedInput = input.trim().removePrefix(name).trim()
+        if (minimumNumberOfArguments > 0 && cleanedInput.isEmpty()) {
+            VimPlugin.showMessage("E471: Argument required")
+            VimPlugin.indicateError()
+            return ""
+        }
+        for (symbol in arrayOf(Count, Arguments, QuotedArguments)) {
+            compiledCommand = compiledCommand.replace(symbol, when (symbol) {
+                Count -> arrayOf(count.toString())
+                Arguments -> arrayOf(cleanedInput)
+                QuotedArguments -> arrayOf("'$cleanedInput'")
+                else -> emptyArray()
+            }.joinToString(", "))
+        }
+
+        // We want to escape <lt> after we've dropped in all of our args, if they are
+        // using <lt> its because they are escaping something that we don't want to handle
+        // yet.
+        compiledCommand = compiledCommand.replace(LessThan, "<")
+
+        return compiledCommand
+    }
+}
\ No newline at end of file
diff --git a/src/com/maddyhome/idea/vim/ex/CommandParser.java b/src/com/maddyhome/idea/vim/ex/CommandParser.java
index 6b768f5b6..98eae6da4 100644
--- a/src/com/maddyhome/idea/vim/ex/CommandParser.java
+++ b/src/com/maddyhome/idea/vim/ex/CommandParser.java
@@ -40,6 +40,7 @@ import java.util.regex.Pattern;
  * executes Ex commands entered by the user.
  */
 public class CommandParser {
+  private static final int MAX_RECURSION = 100;
   public static final int RES_EMPTY = 1;
   public static final int RES_ERROR = 1;
   public static final int RES_READONLY = 1;
@@ -73,7 +74,10 @@ public class CommandParser {
     new ActionListHandler();
     new AsciiHandler();
     new CmdFilterHandler();
+    new CmdHandler();
+    new CmdClearHandler();
     new CopyTextHandler();
+    new DelCmdHandler();
     new DeleteLinesHandler();
     new DigraphHandler();
     new DumpLineHandler();
@@ -165,14 +169,51 @@ public class CommandParser {
    */
   public int processCommand(@NotNull Editor editor, @NotNull DataContext context, @NotNull String cmd,
                             int count) throws ExException {
+    return processCommand(editor, context, cmd, count, MAX_RECURSION);
+  }
+
+  /**
+   * Parse and execute an Ex command entered by the user
+   *
+   * @param editor  The editor to run the command in
+   * @param context The data context
+   * @param cmd     The text entered by the user
+   * @param count   The count entered before the colon
+   * @param aliasCountdown A countdown for the depth of alias recursion that is allowed
+   * @return A bitwise collection of flags, if any, from the result of running the command.
+   * @throws ExException if any part of the command is invalid or unknown
+   */
+  private int processCommand(@NotNull Editor editor, @NotNull DataContext context, @NotNull String cmd,
+                             int count, int aliasCountdown) throws ExException {
     // Nothing entered
     int result = 0;
     if (cmd.length() == 0) {
       return result | RES_EMPTY;
     }
 
-    // Save the command history
-    VimPlugin.getHistory().addEntry(HistoryGroup.COMMAND, cmd);
+    // Only save the command to the history if it is at the top of the stack.
+    // We don't want to save the aliases that will be executed, only the actual
+    // user input.
+    if (aliasCountdown == MAX_RECURSION) {
+      // Save the command history
+      VimPlugin.getHistory().addEntry(HistoryGroup.COMMAND, cmd);
+    }
+
+    // If there is a command alias for the entered text, then process the alias and return that
+    // instead of the original command.
+    if (VimPlugin.getCommand().isAlias(cmd)) {
+      if (aliasCountdown > 0) {
+        String commandAlias = VimPlugin.getCommand().getAliasCommand(cmd, count);
+        if (commandAlias.isEmpty()) {
+          return result |= RES_ERROR;
+        }
+        return processCommand(editor, context, commandAlias, count, aliasCountdown - 1);
+      } else {
+        VimPlugin.showMessage("Recursion detected, maximum alias depth reached.");
+        VimPlugin.indicateError();
+        return result |= RES_ERROR;
+      }
+    }
 
     // Parse the command
     final ExCommand command = parse(cmd);
@@ -192,7 +233,7 @@ public class CommandParser {
     boolean ok = handler.process(editor, context, command, count);
     if (ok && !handler.getArgFlags().getFlags().contains(CommandHandler.Flag.DONT_SAVE_LAST)) {
       VimPlugin.getRegister().storeTextInternal(editor, new TextRange(-1, -1), cmd,
-                                                                  SelectionType.CHARACTER_WISE, ':', false);
+              SelectionType.CHARACTER_WISE, ':', false);
     }
 
     if (handler.getArgFlags().getFlags().contains(CommandHandler.Flag.DONT_REOPEN)) {
diff --git a/src/com/maddyhome/idea/vim/ex/handler/ActionListHandler.kt b/src/com/maddyhome/idea/vim/ex/handler/ActionListHandler.kt
index 966fef695..1566255a7 100644
--- a/src/com/maddyhome/idea/vim/ex/handler/ActionListHandler.kt
+++ b/src/com/maddyhome/idea/vim/ex/handler/ActionListHandler.kt
@@ -34,18 +34,20 @@ class ActionListHandler : CommandHandler(
         flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, DONT_REOPEN)
 ) {
     override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
-        val lineSeparator = System.lineSeparator()
+        val lineSeparator = "\n"
         val searchPattern = cmd.argument.trim().toLowerCase().split("*")
         val actionManager = ActionManager.getInstance()
 
         val actions = actionManager.getActionIds("")
-                .filter { actionName -> searchPattern.all { it in actionName.toLowerCase() } }
-                .sortedWith(String.CASE_INSENSITIVE_ORDER).joinToString(lineSeparator) { actionName ->
+                .sortedWith(String.CASE_INSENSITIVE_ORDER)
+                .map { actionName ->
                     val shortcuts = actionManager.getAction(actionName).shortcutSet.shortcuts.joinToString(" ") {
                         if (it is KeyboardShortcut) StringHelper.toKeyNotation(it.firstKeyStroke) else it.toString()
                     }
                     if (shortcuts.isBlank()) actionName else "${actionName.padEnd(50)} $shortcuts"
                 }
+                .filter { line -> searchPattern.all { it in line.toLowerCase() } }
+                .joinToString(lineSeparator)
 
 
         ExOutputModel.getInstance(editor).output("--- Actions ---$lineSeparator$actions")
diff --git a/src/com/maddyhome/idea/vim/action/ex/BackspaceAction.java b/src/com/maddyhome/idea/vim/ex/handler/CmdClearHandler.kt
similarity index 50%
rename from src/com/maddyhome/idea/vim/action/ex/BackspaceAction.java
rename to src/com/maddyhome/idea/vim/ex/handler/CmdClearHandler.kt
index 55e93abbb..a462e85c6 100644
--- a/src/com/maddyhome/idea/vim/action/ex/BackspaceAction.java
+++ b/src/com/maddyhome/idea/vim/ex/handler/CmdClearHandler.kt
@@ -16,26 +16,22 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-package com.maddyhome.idea.vim.action.ex;
+package com.maddyhome.idea.vim.ex.handler
 
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.actionSystem.EditorAction;
-import com.maddyhome.idea.vim.VimPlugin;
-import com.maddyhome.idea.vim.command.Command;
-import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
-import org.jetbrains.annotations.NotNull;
+import com.intellij.openapi.actionSystem.DataContext
+import com.intellij.openapi.editor.Editor
+import com.maddyhome.idea.vim.VimPlugin
+import com.maddyhome.idea.vim.ex.CommandHandler
+import com.maddyhome.idea.vim.ex.ExCommand
+import com.maddyhome.idea.vim.ex.commands
+import com.maddyhome.idea.vim.ex.flags
 
-/**
- */
-public class BackspaceAction extends EditorAction {
-  public BackspaceAction() {
-    super(new Handler());
-  }
-
-  private static class Handler extends EditorActionHandlerBase {
-    protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
-      return VimPlugin.getProcess().processExKey(editor, cmd.getKeys().get(0));
+class CmdClearHandler : CommandHandler(
+        commands("comc[lear]"),
+        flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_FORBIDDEN)
+) {
+    override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
+        VimPlugin.getCommand().resetAliases()
+        return true
     }
-  }
-}
+}
\ No newline at end of file
diff --git a/src/com/maddyhome/idea/vim/ex/handler/CmdHandler.kt b/src/com/maddyhome/idea/vim/ex/handler/CmdHandler.kt
new file mode 100644
index 000000000..a7db24988
--- /dev/null
+++ b/src/com/maddyhome/idea/vim/ex/handler/CmdHandler.kt
@@ -0,0 +1,177 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.maddyhome.idea.vim.ex.handler
+
+import com.intellij.openapi.actionSystem.DataContext
+import com.intellij.openapi.editor.Editor
+import com.maddyhome.idea.vim.VimPlugin
+import com.maddyhome.idea.vim.common.Alias
+import com.maddyhome.idea.vim.ex.*
+import com.maddyhome.idea.vim.ex.vimscript.VimScriptCommandHandler
+import com.maddyhome.idea.vim.group.CommandGroup.Companion.BLACKLISTED_ALIASES
+
+/**
+ * @author Elliot Courant
+ */
+class CmdHandler : CommandHandler(
+        commands("com[mand]"),
+        flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Flag.DONT_REOPEN)
+), VimScriptCommandHandler {
+    // Static definitions needed for aliases.
+    private companion object {
+        const val overridePrefix = "!"
+        const val argsPrefix = "-nargs"
+
+        const val anyNumberOfArguments = "*"
+        const val zeroOrOneArguments = "?"
+        const val moreThanZeroArguments = "+"
+
+        const val errorInvalidNumberOfArguments = "E176: Invalid number of arguments"
+        const val errorCannotStartWithLowercase = "E183: User defined commands must start with an uppercase letter"
+        const val errorReservedName = "E841: Reserved name, cannot be used for user defined command"
+        const val errorCommandAlreadyExists = "E174: Command already exists: add ! to replace it"
+    }
+
+    override fun execute(cmd: ExCommand) {
+        this.addAlias(cmd, null)
+    }
+
+    override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
+        if (cmd.argument.trim().isEmpty()) {
+            return this.listAlias(editor, "")
+        }
+        return this.addAlias(cmd, editor)
+    }
+
+    private fun listAlias(editor: Editor, filter: String): Boolean {
+        val lineSeparator = "\n"
+        val allAliases = VimPlugin.getCommand().listAliases()
+        val aliases = allAliases.filter {
+            (filter.isEmpty() || it.key.startsWith(filter))
+        }.map {
+            "${it.key.padEnd(12)}${it.value.numberOfArguments.padEnd(11)}${it.value.command}"
+        }.sortedWith(String.CASE_INSENSITIVE_ORDER).joinToString(lineSeparator)
+        ExOutputModel.getInstance(editor).output("Name        Args       Definition$lineSeparator$aliases")
+        return true
+    }
+
+    private fun addAlias(cmd: ExCommand, editor: Editor?): Boolean {
+        var argument = cmd.argument.trim()
+
+        // Handle overwriting of aliases
+        val overrideAlias = argument.startsWith(overridePrefix)
+        if (overrideAlias) {
+            argument = argument.removePrefix(overridePrefix).trim()
+        }
+
+        // Handle alias arguments
+        val hasArguments = argument.startsWith(argsPrefix)
+        var minNumberOfArgs = 0
+        var maxNumberOfArgs = 0
+        if (hasArguments) {
+            // Extract the -nargs that's part of this execution, it's possible that -nargs is
+            // in the actual alias being created, and we don't want to parse that one.
+            val trimmedInput = argument.takeWhile { it != ' ' }
+            val pattern = Regex("(?>-nargs=((|[-])\\d+|[?]|[+]|[*]))").find(trimmedInput) ?: run {
+                VimPlugin.showMessage(errorInvalidNumberOfArguments)
+                return false
+            }
+            val nargForTrim = pattern.groupValues[0]
+            val argumentValue = pattern.groups[1]!!.value
+            val argNum = argumentValue.toIntOrNull()
+            if (argNum == null) { // If the argument number is null then it is not a number.
+                // Make sure the argument value is a valid symbol that we can handle.
+                when (argumentValue) {
+                    anyNumberOfArguments -> {
+                        minNumberOfArgs = 0
+                        maxNumberOfArgs = -1
+                    }
+                    zeroOrOneArguments -> maxNumberOfArgs = 1
+                    moreThanZeroArguments -> {
+                        minNumberOfArgs = 1
+                        maxNumberOfArgs = -1
+                    }
+                    else -> {
+                        // Technically this should never be reached, but is here just in case
+                        // I missed something, since the regex limits the value to be ? + * or
+                        // a valid number, its not possible (as far as I know) to have another value
+                        // that regex would accept that is not valid.
+                        VimPlugin.showMessage(errorInvalidNumberOfArguments)
+                        return false
+                    }
+                }
+            } else {
+                // Not sure why this isn't documented, but if you try to create a command in vim
+                // with an explicit number of arguments greater than 1 it returns this error.
+                if (argNum > 1 || argNum < 0) {
+                    VimPlugin.showMessage(errorInvalidNumberOfArguments)
+                    return false
+                }
+                minNumberOfArgs = argNum
+                maxNumberOfArgs = argNum
+            }
+            argument = argument.removePrefix(nargForTrim).trim()
+        }
+
+        // We want to trim off any "!" at the beginning of the arguments.
+        // This will also remove any extra spaces.
+        argument = argument.trim()
+
+        // We want to get the first character sequence in the arguments.
+        // eg. command! Wq wq
+        // We want to extract the Wq only, and then just use the rest of
+        // the argument as the alias result.
+        val alias = argument.split(" ")[0]
+        argument = argument.removePrefix(alias).trim()
+
+        // User-aliases need to begin with an uppercase character.
+        if (!alias[0].isUpperCase()) {
+            VimPlugin.showMessage(errorCannotStartWithLowercase)
+            return false
+        }
+
+        if (alias in BLACKLISTED_ALIASES) {
+            VimPlugin.showMessage(errorReservedName)
+            return false
+        }
+
+        if (argument.isEmpty()) {
+            if (editor == null) {
+                // If there is no editor then we can't list aliases, just return false.
+                // No message should be shown either, since there is no editor.
+                return false
+            }
+            return this.listAlias(editor, alias)
+        }
+
+        // If we are not over-writing existing aliases, and an alias with the same command
+        // already exists then we want to do nothing.
+        if (!overrideAlias && VimPlugin.getCommand().hasAlias(alias)) {
+            VimPlugin.showMessage(errorCommandAlreadyExists)
+            return false
+        }
+
+        // Store the alias and the command. We don't need to parse the argument
+        // at this time, if the syntax is wrong an error will be returned when
+        // the alias is executed.
+        VimPlugin.getCommand().setAlias(alias, Alias(minNumberOfArgs, maxNumberOfArgs, alias, argument))
+
+        return true
+    }
+}
\ No newline at end of file
diff --git a/src/com/maddyhome/idea/vim/ex/handler/DelCmdHandler.kt b/src/com/maddyhome/idea/vim/ex/handler/DelCmdHandler.kt
new file mode 100644
index 000000000..be9c87c58
--- /dev/null
+++ b/src/com/maddyhome/idea/vim/ex/handler/DelCmdHandler.kt
@@ -0,0 +1,42 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.maddyhome.idea.vim.ex.handler
+
+import com.intellij.openapi.actionSystem.DataContext
+import com.intellij.openapi.editor.Editor
+import com.maddyhome.idea.vim.VimPlugin
+import com.maddyhome.idea.vim.ex.CommandHandler
+import com.maddyhome.idea.vim.ex.ExCommand
+import com.maddyhome.idea.vim.ex.commands
+import com.maddyhome.idea.vim.ex.flags
+
+class DelCmdHandler : CommandHandler(
+        commands("delc[ommand]"),
+        flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_REQUIRED)
+) {
+    override fun execute(editor: Editor, context: DataContext, cmd: ExCommand): Boolean {
+        if (!VimPlugin.getCommand().hasAlias(cmd.argument)) {
+            VimPlugin.showMessage("E184: No such user-defined command: ${cmd.argument}")
+            return false
+        }
+
+        VimPlugin.getCommand().removeAlias(cmd.argument)
+        return true
+    }
+}
\ No newline at end of file
diff --git a/src/com/maddyhome/idea/vim/group/ChangeGroup.java b/src/com/maddyhome/idea/vim/group/ChangeGroup.java
index bb0dfa683..e19260bc9 100644
--- a/src/com/maddyhome/idea/vim/group/ChangeGroup.java
+++ b/src/com/maddyhome/idea/vim/group/ChangeGroup.java
@@ -1069,20 +1069,20 @@ public class ChangeGroup {
     if (motion == null) {
       return false;
     }
+    EnumSet<CommandFlags> flags = motion.getFlags().clone();
     if (!isChange && !motion.getFlags().contains(CommandFlags.FLAG_MOT_LINEWISE)) {
       LogicalPosition start = editor.offsetToLogicalPosition(range.getStartOffset());
       LogicalPosition end = editor.offsetToLogicalPosition(range.getEndOffset());
       if (start.line != end.line) {
         if (!SearchHelper.anyNonWhitespace(editor, range.getStartOffset(), -1) &&
             !SearchHelper.anyNonWhitespace(editor, range.getEndOffset(), 1)) {
-          EnumSet<CommandFlags> flags = motion.getFlags();
           flags.remove(CommandFlags.FLAG_MOT_EXCLUSIVE);
           flags.remove(CommandFlags.FLAG_MOT_INCLUSIVE);
           flags.add(CommandFlags.FLAG_MOT_LINEWISE);
         }
       }
     }
-    return deleteRange(editor, caret, range, SelectionType.fromCommandFlags(motion.getFlags()), isChange);
+    return deleteRange(editor, caret, range, SelectionType.fromCommandFlags(flags), isChange);
   }
 
   /**
@@ -1678,7 +1678,7 @@ public class ChangeGroup {
       return false;
     }
 
-    if (type == null || VimPlugin.getRegister().storeText(editor, range, type, true)) {
+    if (type == null || CommandState.inInsertMode(editor) || VimPlugin.getRegister().storeText(editor, range, type, true)) {
       final Document document = editor.getDocument();
       final int[] startOffsets = range.getStartOffsets();
       final int[] endOffsets = range.getEndOffsets();
diff --git a/src/com/maddyhome/idea/vim/group/CommandGroup.kt b/src/com/maddyhome/idea/vim/group/CommandGroup.kt
new file mode 100644
index 000000000..6fcaa2cc6
--- /dev/null
+++ b/src/com/maddyhome/idea/vim/group/CommandGroup.kt
@@ -0,0 +1,84 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.maddyhome.idea.vim.group
+
+import com.maddyhome.idea.vim.common.Alias
+
+/**
+ * @author Elliot Courant
+ */
+class CommandGroup {
+    companion object {
+        val BLACKLISTED_ALIASES = arrayOf("X", "Next", "Print")
+        private const val overridePrefix = "!"
+    }
+    private var aliases = HashMap<String, Alias>()
+
+    fun isAlias(command: String): Boolean {
+        val name = this.getAliasName(command)
+        // If the first letter is not uppercase then it cannot be an alias
+        // and reject immediately.
+        if (!name[0].isUpperCase()) {
+            return false
+        }
+
+        // If the input is blacklisted, then it is not an alias.
+        if (name in BLACKLISTED_ALIASES) {
+            return false
+        }
+
+        return this.hasAlias(name)
+    }
+
+    fun hasAlias(name: String): Boolean {
+        return name in this.aliases
+    }
+
+    fun getAlias(name: String): Alias {
+        return this.aliases[name]!!
+    }
+
+    fun getAliasCommand(command: String, count: Int): String {
+        return this.getAlias(this.getAliasName(command)).getCommand(command, count)
+    }
+
+    fun setAlias(name: String, alias: Alias) {
+        this.aliases[name] = alias
+    }
+
+    fun removeAlias(name: String) {
+        this.aliases.remove(name)
+    }
+
+    fun listAliases(): Set<Map.Entry<String, Alias>> {
+        return this.aliases.entries
+    }
+
+    fun resetAliases() {
+        this.aliases.clear()
+    }
+
+    private fun getAliasName(command: String): String {
+        val items = command.split(" ")
+        if (items.count() > 1) {
+            return items[0].removeSuffix(overridePrefix)
+        }
+        return command.removeSuffix(overridePrefix)
+    }
+}
\ No newline at end of file
diff --git a/src/com/maddyhome/idea/vim/group/EditorGroup.java b/src/com/maddyhome/idea/vim/group/EditorGroup.java
index 65e0581e3..4c9fdad7e 100644
--- a/src/com/maddyhome/idea/vim/group/EditorGroup.java
+++ b/src/com/maddyhome/idea/vim/group/EditorGroup.java
@@ -28,7 +28,6 @@ import com.intellij.openapi.editor.event.*;
 import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.project.Project;
 import com.maddyhome.idea.vim.EventFacade;
-import com.maddyhome.idea.vim.KeyHandler;
 import com.maddyhome.idea.vim.VimPlugin;
 import com.maddyhome.idea.vim.command.CommandState;
 import com.maddyhome.idea.vim.helper.*;
@@ -39,7 +38,6 @@ import org.jdom.Element;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import javax.swing.*;
 import java.awt.*;
 import java.util.List;
 
@@ -94,7 +92,7 @@ public class EditorGroup {
           // Turn on insert mode if editor doesn't have any file
           if (!EditorData.isFileEditor(editor) && editor.getDocument().isWritable() &&
               !CommandState.inInsertMode(editor)) {
-            KeyHandler.getInstance().handleKey(editor, KeyStroke.getKeyStroke('i'), new EditorDataContext(editor));
+            VimPlugin.getChange().insertBeforeCursor(editor, new EditorDataContext(editor));
           }
           editor.getSettings().setBlockCursor(!CommandState.inInsertMode(editor));
           editor.getSettings().setAnimatedScrolling(ANIMATED_SCROLLING_VIM_VALUE);
diff --git a/src/com/maddyhome/idea/vim/group/KeyGroup.java b/src/com/maddyhome/idea/vim/group/KeyGroup.java
index 051d48421..5adb5d422 100644
--- a/src/com/maddyhome/idea/vim/group/KeyGroup.java
+++ b/src/com/maddyhome/idea/vim/group/KeyGroup.java
@@ -68,13 +68,12 @@ public class KeyGroup {
   @NotNull private final Map<MappingMode, KeyMapping> keyMappings = new HashMap<>();
   @Nullable private OperatorFunction operatorFunction = null;
 
-  public void registerRequiredShortcutKeys(@NotNull Editor editor) {
-    final Set<KeyStroke> requiredKeys = VimPlugin.getKey().requiredShortcutKeys;
+  void registerRequiredShortcutKeys(@NotNull Editor editor) {
     EventFacade.getInstance().registerCustomShortcutSet(VimShortcutKeyAction.getInstance(),
-                                                        toShortcutSet(requiredKeys), editor.getComponent());
+                                                        toShortcutSet(requiredShortcutKeys), editor.getComponent());
   }
 
-  public void unregisterShortcutKeys(@NotNull Editor editor) {
+  void unregisterShortcutKeys(@NotNull Editor editor) {
     EventFacade.getInstance().unregisterCustomShortcutSet(VimShortcutKeyAction.getInstance(), editor.getComponent());
   }
 
diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java
index da9f7f6d2..bdfbc59b2 100755
--- a/src/com/maddyhome/idea/vim/group/MotionGroup.java
+++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java
@@ -54,6 +54,7 @@ import com.maddyhome.idea.vim.listener.VimListenerManager;
 import com.maddyhome.idea.vim.option.NumberOption;
 import com.maddyhome.idea.vim.option.Options;
 import com.maddyhome.idea.vim.ui.ExEntryPanel;
+import kotlin.ranges.IntProgression;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -1055,7 +1056,21 @@ public class MotionGroup {
 
   public int moveCaretHorizontal(@NotNull Editor editor, @NotNull Caret caret, int count, boolean allowPastEnd) {
     int oldOffset = caret.getOffset();
-    int offset = EditorHelper.normalizeOffset(editor, caret.getLogicalPosition().line, oldOffset + count, allowPastEnd);
+    int diff = 0;
+    String text = editor.getDocument().getText();
+    int sign = (int)Math.signum(count);
+    for (Integer pointer : new IntProgression(0, count - sign, sign)) {
+      int textPointer = oldOffset + pointer;
+      if (textPointer < text.length() && textPointer >= 0) {
+        // Actual char size can differ from 1 if unicode characters are used (like 🐔)
+        diff += Character.charCount(text.codePointAt(textPointer));
+      }
+      else {
+        diff += 1;
+      }
+    }
+    int offset =
+      EditorHelper.normalizeOffset(editor, caret.getLogicalPosition().line, oldOffset + (sign * diff), allowPastEnd);
 
     if (offset == oldOffset) {
       return -1;
diff --git a/src/com/maddyhome/idea/vim/group/ProcessGroup.java b/src/com/maddyhome/idea/vim/group/ProcessGroup.java
index 92e104fc3..00f45373c 100644
--- a/src/com/maddyhome/idea/vim/group/ProcessGroup.java
+++ b/src/com/maddyhome/idea/vim/group/ProcessGroup.java
@@ -90,7 +90,7 @@ public class ProcessGroup {
 
     ExEntryPanel panel = ExEntryPanel.getInstance();
     if (panel.isActive()) {
-      UiHelper.requestFocus(panel);
+      UiHelper.requestFocus(panel.getEntry());
       panel.handleKey(stroke);
 
       return true;
@@ -144,13 +144,11 @@ public class ProcessGroup {
     return res;
   }
 
-  public boolean cancelExEntry(@NotNull final Editor editor, @NotNull final DataContext context) {
+  public void cancelExEntry(@NotNull final Editor editor, @NotNull final DataContext context) {
     CommandState.getInstance(editor).popState();
     KeyHandler.getInstance().reset(editor);
     ExEntryPanel panel = ExEntryPanel.getInstance();
     panel.deactivate(true);
-
-    return true;
   }
 
   private void record(Editor editor, @NotNull String text) {
diff --git a/src/com/maddyhome/idea/vim/helper/DigraphSequence.java b/src/com/maddyhome/idea/vim/helper/DigraphSequence.java
index 077a1cbed..378a33591 100644
--- a/src/com/maddyhome/idea/vim/helper/DigraphSequence.java
+++ b/src/com/maddyhome/idea/vim/helper/DigraphSequence.java
@@ -49,7 +49,7 @@ public class DigraphSequence {
         if (key.getKeyCode() == KeyEvent.VK_K && (key.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
           logger.debug("found Ctrl-K");
           digraphState = DIG_STATE_DIG_ONE;
-          return DigraphResult.OK;
+          return DigraphResult.OK_DIGRAPH;
         }
         else if ((key.getKeyCode() == KeyEvent.VK_V || key.getKeyCode() == KeyEvent.VK_Q) &&
                  (key.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
@@ -57,7 +57,7 @@ public class DigraphSequence {
           digraphState = DIG_STATE_CODE_START;
           codeChars = new char[8];
           codeCnt = 0;
-          return DigraphResult.OK;
+          return DigraphResult.OK_LITERAL;
         }
         else {
           return new DigraphResult(key);
@@ -68,7 +68,7 @@ public class DigraphSequence {
           digraphChar = key.getKeyChar();
           digraphState = DIG_STATE_DIG_TWO;
 
-          return DigraphResult.OK;
+          return new DigraphResult(DigraphResult.RES_OK, digraphChar);
         }
         else {
           digraphState = DIG_STATE_START;
@@ -93,26 +93,26 @@ public class DigraphSequence {
             digraphState = DIG_STATE_CODE_CHAR;
             codeType = 8;
             logger.debug("Octal");
-            return DigraphResult.OK;
+            return DigraphResult.OK_LITERAL;
           case 'x':
           case 'X':
             codeMax = 2;
             digraphState = DIG_STATE_CODE_CHAR;
             codeType = 16;
             logger.debug("hex2");
-            return DigraphResult.OK;
+            return DigraphResult.OK_LITERAL;
           case 'u':
             codeMax = 4;
             digraphState = DIG_STATE_CODE_CHAR;
             codeType = 16;
             logger.debug("hex4");
-            return DigraphResult.OK;
+            return DigraphResult.OK_LITERAL;
           case 'U':
             codeMax = 8;
             digraphState = DIG_STATE_CODE_CHAR;
             codeType = 16;
             logger.debug("hex8");
-            return DigraphResult.OK;
+            return DigraphResult.OK_LITERAL;
           case '0':
           case '1':
           case '2':
@@ -128,7 +128,7 @@ public class DigraphSequence {
             codeType = 10;
             codeChars[codeCnt++] = key.getKeyChar();
             logger.debug("decimal");
-            return DigraphResult.OK;
+            return DigraphResult.OK_LITERAL;
           default:
             switch (key.getKeyCode()) {
               case KeyEvent.VK_TAB:
@@ -177,7 +177,7 @@ public class DigraphSequence {
             return new DigraphResult(code);
           }
           else {
-            return DigraphResult.OK;
+            return DigraphResult.OK_LITERAL;
           }
         }
         else if (codeCnt > 0) {
@@ -204,14 +204,21 @@ public class DigraphSequence {
     public static final int RES_BAD = 1;
     public static final int RES_DONE = 2;
 
-    public static final DigraphResult OK = new DigraphResult(RES_OK);
-    public static final DigraphResult BAD = new DigraphResult(RES_BAD);
+    static final DigraphResult OK_DIGRAPH = new DigraphResult(RES_OK, '?');
+    static final DigraphResult OK_LITERAL = new DigraphResult(RES_OK, '^');
+    static final DigraphResult BAD = new DigraphResult(RES_BAD);
 
     DigraphResult(int result) {
       this.result = result;
       stroke = null;
     }
 
+    DigraphResult(int result, char promptCharacter) {
+      this.result = result;
+      this.promptCharacter = promptCharacter;
+      stroke = null;
+    }
+
     DigraphResult(@Nullable KeyStroke stroke) {
       result = RES_DONE;
       this.stroke = stroke;
@@ -226,8 +233,13 @@ public class DigraphSequence {
       return result;
     }
 
+    public char getPromptCharacter() {
+      return promptCharacter;
+    }
+
     private final int result;
     @Nullable private final KeyStroke stroke;
+    private char promptCharacter;
   }
 
   private int digraphState = DIG_STATE_START;
diff --git a/src/com/maddyhome/idea/vim/helper/SearchHelper.java b/src/com/maddyhome/idea/vim/helper/SearchHelper.java
index 2ba37061f..d8be3a659 100644
--- a/src/com/maddyhome/idea/vim/helper/SearchHelper.java
+++ b/src/com/maddyhome/idea/vim/helper/SearchHelper.java
@@ -18,7 +18,6 @@
 
 package com.maddyhome.idea.vim.helper;
 
-import com.google.common.collect.Lists;
 import com.intellij.lang.CodeDocumentationAwareCommenter;
 import com.intellij.lang.Commenter;
 import com.intellij.lang.Language;
@@ -27,7 +26,6 @@ import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Caret;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.psi.PsiComment;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
@@ -42,6 +40,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Stack;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -115,13 +114,32 @@ public class SearchHelper {
     int pos = caret.getOffset();
     int start = caret.getSelectionStart();
     int end = caret.getSelectionEnd();
-    if (start != end) {
-      pos = Math.min(start, end);
-    }
 
     int loc = blockChars.indexOf(type);
     char close = blockChars.charAt(loc + 1);
 
+    boolean rangeSelection = end - start > 1;
+    if (rangeSelection && start == 0) // early return not only for optimization
+      return null;                    // but also not to break the interval semantic on this edge case (see below)
+
+    /* In case of successive inner selection. We want to break out of
+     * the block delimiter of the current inner selection.
+     * In other terms, for the rest of the algorithm, a previous inner selection of a block
+     * if equivalent to an outer one. */
+    if (!isOuter
+        && (start - 1) >= 0 && type == chars.charAt(start - 1)
+        && end < chars.length() && close == chars.charAt(end)) {
+      start = start - 1;
+      pos = start;
+      rangeSelection = true;
+    }
+
+    /* when one char is selected, we want to find the enclosing block of (start,end]
+     * although when a range of characters is selected, we want the enclosing block of [start, end]
+     * shifting the position allow to express which kind of interval we work on */
+    if (rangeSelection)
+      pos = Math.max(0, start - 1);
+
     boolean initialPosIsInString = checkInString(chars, pos, true);
 
     int bstart = -1;
@@ -413,80 +431,192 @@ public class SearchHelper {
     return -1;
   }
 
+  /** returns new position which ignore whitespaces at beginning of the line*/
+  private static int ignoreWhitespaceAtLineStart(CharSequence seq, int lineStart, int pos) {
+    if (seq.subSequence(lineStart, pos).chars().allMatch(Character::isWhitespace)) {
+      while (pos < seq.length() && seq.charAt(pos) != '\n' && Character.isWhitespace(seq.charAt(pos))) {
+        pos++;
+      }
+    }
+    return pos;
+  }
+
+
   @Nullable
   public static TextRange findBlockTagRange(@NotNull Editor editor, @NotNull Caret caret, int count, boolean isOuter) {
-    final int cursorOffset = caret.getOffset();
-    int pos = cursorOffset;
-    int currentCount = count;
+    final int position = caret.getOffset();
     final CharSequence sequence = editor.getDocument().getCharsSequence();
+
+    final int selectionStart = caret.getSelectionStart();
+    final int selectionEnd = caret.getSelectionEnd();
+
+    final boolean isRangeSelection = selectionEnd - selectionStart > 1;
+
+    int searchStartPosition;
+    if (!isRangeSelection) {
+      final int line = caret.getLogicalPosition().line;
+      final int lineBegin = editor.getDocument().getLineStartOffset(line);
+      searchStartPosition = ignoreWhitespaceAtLineStart(sequence, lineBegin, position);
+    } else {
+      searchStartPosition = selectionEnd;
+    }
+
+    if (isInHTMLTag(sequence, searchStartPosition, false)) {
+      // caret is inside opening tag. Move to closing '>'.
+      while (searchStartPosition < sequence.length() && sequence.charAt(searchStartPosition) != '>') {
+        searchStartPosition ++;
+      }
+    }
+    else if (isInHTMLTag(sequence, searchStartPosition, true)) {
+      // caret is inside closing tag. Move to starting '<'.
+      while (searchStartPosition > 0 && sequence.charAt(searchStartPosition) != '<') {
+        searchStartPosition --;
+      }
+    }
+
     while (true) {
-      final Pair<TextRange, String> closingTagResult = findClosingTag(sequence, pos);
-      if (closingTagResult == null) {
+      final Pair<TextRange, String> closingTag = findUnmatchedClosingTag(sequence, searchStartPosition, count);
+      if (closingTag == null) {
         return null;
       }
-      final TextRange closingTagTextRange = closingTagResult.getFirst();
-      final String tagName = closingTagResult.getSecond();
-      final TextRange openingTagTextRange = findOpeningTag(sequence, closingTagTextRange.getStartOffset(), tagName);
-      if (openingTagTextRange != null && openingTagTextRange.getStartOffset() <= cursorOffset && --currentCount == 0) {
-        if (isOuter) {
-          return new TextRange(openingTagTextRange.getStartOffset(), closingTagTextRange.getEndOffset());
+      final TextRange closingTagTextRange = closingTag.getFirst();
+      final String tagName = closingTag.getSecond();
+
+      TextRange openingTag = findUnmatchedOpeningTag(sequence, closingTagTextRange.getStartOffset(), tagName);
+      if (openingTag == null) {
+        return null;
+      }
+
+      if (isRangeSelection && openingTag.getEndOffset() - 1 >= selectionStart) {
+        // If there was already some text selected and the new selection would not extend further, we try again
+        searchStartPosition = closingTagTextRange.getEndOffset();
+        count = 1;
+        continue;
+      }
+
+      int selectionEndWithoutNewline = selectionEnd;
+      while (selectionEndWithoutNewline < sequence.length() && sequence.charAt(selectionEndWithoutNewline) == '\n') {
+        selectionEndWithoutNewline ++;
+      }
+
+      if (closingTagTextRange.getStartOffset() == selectionEndWithoutNewline && openingTag.getEndOffset() == selectionStart) {
+        // Special case: if the inner tag is already selected we should like isOuter is active
+        // Note that we need to ignore newlines, because their selection is lost between multiple "it" invocations
+        isOuter = true;
+      } else
+      if (openingTag.getEndOffset() == closingTagTextRange.getStartOffset() && selectionStart == openingTag.getEndOffset()) {
+        // Special case: for an empty tag pair (e.g. <a></a>) the whole tag is selected if the caret is in the middle.
+        isOuter = true;
+      }
+
+      if (isOuter) {
+        return new TextRange(openingTag.getStartOffset(), closingTagTextRange.getEndOffset() - 1);
+      } else {
+        return new TextRange(openingTag.getEndOffset(), Math.max(closingTagTextRange.getStartOffset() - 1, openingTag.getEndOffset()));
+      }
+    }
+  }
+
+  /**
+   * Returns true if there is a html at the given position. Ignores tags with a trailing slash like <aaa/>.
+   */
+  private static boolean isInHTMLTag(@NotNull final CharSequence sequence, final int position, final boolean isEndtag) {
+    int openingBracket = -1;
+    for (int i = position; i >= 0 && i < sequence.length(); i--) {
+      if (sequence.charAt(i) == '<') {
+        openingBracket = i;
+        break;
+      }
+      if (sequence.charAt(i) == '>' && i != position) {
+        return false;
+      }
+    }
+
+    if (openingBracket == -1) {
+      return false;
+    }
+
+    boolean hasSlashAfterOpening = openingBracket + 1 < sequence.length() && sequence.charAt(openingBracket + 1) == '/';
+    if ((isEndtag && !hasSlashAfterOpening) || (!isEndtag && hasSlashAfterOpening)) {
+      return false;
+    }
+
+    int closingBracket = -1;
+    for (int i = openingBracket; i < sequence.length(); i++) {
+      if (sequence.charAt(i) == '>') {
+        closingBracket = i;
+        break;
+      }
+    }
+
+    return closingBracket != -1 && sequence.charAt(closingBracket - 1) != '/';
+  }
+
+  @Nullable
+  private static Pair<TextRange,String> findUnmatchedClosingTag(@NotNull final CharSequence sequence, final int position, int count) {
+    // The tag name may contain any characters except slashes, whitespace and '>'
+    final String tagNamePattern = "([^/\\s>]+)";
+    // An opening tag consists of '<' followed by a tag name, optionally some additional text after whitespace and a '>'
+    final String openingTagPattern = String.format("<%s(?:\\s[^>]*)?>", tagNamePattern);
+    final String closingTagPattern = String.format("</%s>", tagNamePattern);
+    final Pattern tagPattern = Pattern.compile(String.format("(?:%s)|(?:%s)", openingTagPattern, closingTagPattern));
+    final Matcher matcher = tagPattern.matcher(sequence.subSequence(position, sequence.length()));
+
+    final Stack<String> openTags = new Stack<>();
+
+    while (matcher.find()) {
+      boolean isClosingTag = matcher.group(1) == null;
+      if (isClosingTag) {
+        final String tagName = matcher.group(2);
+        // Ignore unmatched open tags. Either the file is malformed or it might be a tag like <br> that does not need to be closed.
+        while (!openTags.isEmpty() && !openTags.peek().equalsIgnoreCase(tagName)) {
+          openTags.pop();
         }
-        else {
-          return new TextRange(openingTagTextRange.getEndOffset() + 1, closingTagTextRange.getStartOffset() - 1);
+        if (openTags.isEmpty()) {
+          if (count <= 1) {
+            return Pair.create(new TextRange(position + matcher.start(), position + matcher.end()), tagName);
+          } else {
+            count--;
+          }
+        } else {
+          openTags.pop();
+        }
+      } else {
+        final String tagName = matcher.group(1);
+        openTags.push(tagName);
+      }
+    }
+    return null;
+  }
+
+  @Nullable
+  private static TextRange findUnmatchedOpeningTag(@NotNull CharSequence sequence, int position, @NotNull String tagName) {
+    final String quotedTagName = Pattern.quote(tagName);
+    final String patternString = "(</%s>)"  // match closing tags
+            + "|(<%s"      // or opening tags starting with tagName
+            + "(\\s([^>]*"  // After at least one whitespace there might be additional text in the tag. E.g. <html lang="en">
+            + "[^/])?)?>)";  // Slash is not allowed as last character (this would be a self closing tag).
+    final Pattern tagPattern = Pattern.compile(String.format(patternString, quotedTagName, quotedTagName), Pattern.CASE_INSENSITIVE);
+    final Matcher matcher = tagPattern.matcher(sequence.subSequence(0, position+1));
+    final Stack<TextRange> openTags = new Stack<>();
+
+    while (matcher.find()) {
+      final TextRange match = new TextRange(matcher.start(), matcher.end());
+      if (sequence.charAt(matcher.start() + 1) == '/') {
+        if (!openTags.isEmpty()) {
+          openTags.pop();
         }
       }
       else {
-        pos = closingTagTextRange.getEndOffset() + 1;
+        openTags.push(match);
       }
     }
-  }
 
-  @Nullable
-  private static TextRange findOpeningTag(@NotNull CharSequence sequence, int position, @NotNull String tagName) {
-    final String tagBeginning = "<" + tagName;
-    final Pattern pattern = Pattern.compile(Pattern.quote(tagBeginning), Pattern.CASE_INSENSITIVE);
-    final Matcher matcher = pattern.matcher(sequence.subSequence(0, position));
-    final List<Integer> possibleBeginnings = Lists.newArrayList();
-    while (matcher.find()) {
-      possibleBeginnings.add(matcher.start());
+    if (openTags.isEmpty()) {
+      return null;
+    } else {
+      return openTags.pop();
     }
-    final List<Integer> reversedBeginnings = Lists.reverse(possibleBeginnings);
-    for (int openingTagPos : reversedBeginnings) {
-      final int openingTagEndPos = openingTagPos + tagBeginning.length();
-      final int closeBracketPos = StringUtil.indexOf(sequence, '>', openingTagEndPos);
-      if (closeBracketPos > 0 && (closeBracketPos == openingTagEndPos || sequence.charAt(openingTagEndPos) == ' ')) {
-        return new TextRange(openingTagPos, closeBracketPos);
-      }
-    }
-    return null;
-  }
-
-  @Nullable
-  private static Pair<TextRange, String> findClosingTag(@NotNull CharSequence sequence, int pos) {
-    int closeBracketPos = pos;
-    int openBracketPos;
-    while (closeBracketPos < sequence.length()) {
-      closeBracketPos = StringUtil.indexOf(sequence, '>', closeBracketPos);
-      if (closeBracketPos < 0) {
-        return null;
-      }
-      openBracketPos = closeBracketPos - 1;
-      while (openBracketPos >= 0) {
-        openBracketPos = StringUtil.lastIndexOf(sequence, '<', 0, openBracketPos);
-        if (openBracketPos >= 0 &&
-            openBracketPos + 1 < sequence.length() &&
-            sequence.charAt(openBracketPos + 1) == '/') {
-          final String tagName = String.valueOf(sequence.subSequence(openBracketPos + "</".length(), closeBracketPos));
-          if (tagName.length() > 0 && tagName.charAt(0) != ' ') {
-            TextRange textRange = new TextRange(openBracketPos, closeBracketPos);
-            return Pair.create(textRange, tagName);
-          }
-        }
-        openBracketPos--;
-      }
-      closeBracketPos++;
-    }
-    return null;
   }
 
 
diff --git a/src/com/maddyhome/idea/vim/listener/ListenerManager.kt b/src/com/maddyhome/idea/vim/listener/ListenerManager.kt
index c1546b576..b351b40e1 100644
--- a/src/com/maddyhome/idea/vim/listener/ListenerManager.kt
+++ b/src/com/maddyhome/idea/vim/listener/ListenerManager.kt
@@ -190,7 +190,7 @@ object VimListenerManager {
                 VimPlugin.getMotion()
                 val editor = event.editor
                 if (ExEntryPanel.getInstance().isActive) {
-                    ExEntryPanel.getInstance().deactivate(false)
+                    VimPlugin.getProcess().cancelExEntry(editor, ExEntryPanel.getInstance().entry.context)
                 }
 
                 ExOutputModel.getInstance(editor).clear()
@@ -226,7 +226,7 @@ object VimListenerManager {
                     event.mouseEvent.button != MouseEvent.BUTTON3) {
                 VimPlugin.getMotion()
                 if (ExEntryPanel.getInstance().isActive) {
-                    ExEntryPanel.getInstance().deactivate(false)
+                    VimPlugin.getProcess().cancelExEntry(event.editor, ExEntryPanel.getInstance().entry.context)
                 }
 
                 ExOutputModel.getInstance(event.editor).clear()
diff --git a/src/com/maddyhome/idea/vim/package-info.java b/src/com/maddyhome/idea/vim/package-info.java
index 5532b64f9..965bec805 100644
--- a/src/com/maddyhome/idea/vim/package-info.java
+++ b/src/com/maddyhome/idea/vim/package-info.java
@@ -307,6 +307,10 @@
  * |CTRL-W_<Up>|          {@link com.maddyhome.idea.vim.action.window.WindowUpAction}
  * |CTRL-W_<Left>|        {@link com.maddyhome.idea.vim.action.window.WindowLeftAction}
  * |CTRL-W_<Right>|       {@link com.maddyhome.idea.vim.action.window.WindowRightAction}
+ * |CTRL-W_CTRL-H|        {@link com.maddyhome.idea.vim.action.window.WindowLeftAction}
+ * |CTRL-W_CTRL-J|        {@link com.maddyhome.idea.vim.action.window.WindowDownAction}
+ * |CTRL-W_CTRL-K|        {@link com.maddyhome.idea.vim.action.window.WindowUpAction}
+ * |CTRL-W_CTRL-L|        {@link com.maddyhome.idea.vim.action.window.WindowRightAction}
  *
  *
  * 2.3. Square bracket commands
@@ -594,7 +598,72 @@
  *
  * 5. Command line editing
  *
- * There is no up-to-date list of supported command line editing commands.
+ * tag                    action
+ * -------------------------------------------------------------------------------------------------------------------
+ *
+ * |c_CTRL-A|             TODO
+ * |c_CTRL-B|             {@link javax.swing.text.DefaultEditorKit#beginLineAction}
+ * |c_CTRL-C|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.CancelEntryAction}
+ * |c_CTRL-D|             TODO
+ * |c_CTRL-E|             {@link javax.swing.text.DefaultEditorKit#endLineAction}
+ * |c_CTRL-G|             TODO
+ * |c_CTRL-H|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.DeletePreviousCharAction}
+ * |c_CTRL-I|             TODO
+ * |c_CTRL-J|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.CompleteEntryAction}
+ * |c_CTRL-K|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
+ * |c_CTRL-L|             TODO
+ * |c_CTRL-M|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.CompleteEntryAction}
+ * |c_CTRL-N|             TODO
+ * |c_CTRL-P|             TODO
+ * |c_CTRL-Q|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
+ * |c_CTRL-R|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.InsertRegisterAction}
+ * |c_CTRL-R_CTRL-A|      TODO
+ * |c_CTRL-R_CTRL-F|      TODO
+ * |c_CTRL-R_CTRL-L|      TODO
+ * |c_CTRL-R_CTRL-O|      TODO
+ * |c_CTRL-R_CTRL-P|      TODO
+ * |c_CTRL-R_CTRL-R|      TODO
+ * |c_CTRL-R_CTRL-W|      TODO
+ * |c_CTRL-T|             TODO
+ * |c_CTRL-U|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.DeleteToCursorAction}
+ * |c_CTRL-V|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
+ * |c_CTRL-W|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.DeletePreviousWordAction}
+ * |c_CTRL-Y|             TODO
+ * |c_CTRL-\_e|           TODO
+ * |c_CTRL-\_CTRL-G|      TODO
+ * |c_CTRL-\_CTRL-N|      TODO
+ * |c_CTRL-_|             not applicable
+ * |c_CTRL-^|             not applicable
+ * |c_CTRL-]|             TODO
+ * |c_CTRL-[|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.EscapeCharAction}
+ * |c_<BS>|               {@link com.maddyhome.idea.vim.ui.ExEditorKit.DeletePreviousCharAction}
+ * |c_<CR>|               {@link com.maddyhome.idea.vim.ui.ExEditorKit.CompleteEntryAction}
+ * |c_<C-Left>|           {@link javax.swing.text.DefaultEditorKit#previousWordAction}
+ * |c_<C-Right>|          {@link javax.swing.text.DefaultEditorKit#nextWordAction}
+ * |c_<Del>|              {@link javax.swing.text.DefaultEditorKit#deleteNextCharAction}
+ * |c_<Down>|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryDownFilterAction}
+ * |c_<End>|              {@link javax.swing.text.DefaultEditorKit#endLineAction}
+ * |c_<Esc>|              {@link com.maddyhome.idea.vim.ui.ExEditorKit.EscapeCharAction}
+ * |c_<Home>|             {@link javax.swing.text.DefaultEditorKit#beginLineAction}
+ * |c_<Insert>|           {@link com.maddyhome.idea.vim.ui.ExEditorKit.ToggleInsertReplaceAction}
+ * |c_<Left>|             {@link javax.swing.text.DefaultEditorKit#backwardAction}
+ * |c_<LeftMouse>|        not applicable
+ * |c_<MiddleMouse>|      TODO
+ * |c_<NL>|               {@link com.maddyhome.idea.vim.ui.ExEditorKit.CompleteEntryAction}
+ * |c_<PageUp>|           {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryUpAction}
+ * |c_<PageDown>|         {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryDownAction}
+ * |c_<Right>|            {@link javax.swing.text.DefaultEditorKit#forwardAction}
+ * |c_<S-Down>|           {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryDownAction}
+ * |c_<S-Left>|           {@link javax.swing.text.DefaultEditorKit#previousWordAction}
+ * |c_<S-Right>|          {@link javax.swing.text.DefaultEditorKit#nextWordAction}
+ * |c_<S-Tab>|            TODO
+ * |c_<S-Up>|             {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryUpAction}
+ * |c_<Tab>|              TODO
+ * |c_<Up>|               {@link com.maddyhome.idea.vim.ui.ExEditorKit.HistoryUpFilterAction}
+ * |c_digraph|            {char1} <BS> {char2} {@link com.maddyhome.idea.vim.ui.ExEditorKit.StartDigraphAction}
+ * |c_wildchar|           TODO
+ * |'cedit'|              TODO
+ *
  *
  * 6. Ex commands
  *
@@ -621,6 +690,9 @@
  * |:quitall|             {@link com.maddyhome.idea.vim.ex.handler.ExitHandler}
  * |:wqall|               {@link com.maddyhome.idea.vim.ex.handler.ExitHandler}
  * |:xall|                {@link com.maddyhome.idea.vim.ex.handler.ExitHandler}
+ * |:command|             {@link com.maddyhome.idea.vim.ex.handler.CmdHandler} [To Be Released]
+ * |:delcommand|          {@link com.maddyhome.idea.vim.ex.handler.DelCmdHandler} [To Be Released]
+ * |:comclear|            {@link com.maddyhome.idea.vim.ex.handler.CmdClearHandler} [To Be Released]
  * ...
  *
  * The list of supported Ex commands is incomplete.
diff --git a/src/com/maddyhome/idea/vim/ui/ExDocument.java b/src/com/maddyhome/idea/vim/ui/ExDocument.java
index 73b33bc6f..b908810da 100644
--- a/src/com/maddyhome/idea/vim/ui/ExDocument.java
+++ b/src/com/maddyhome/idea/vim/ui/ExDocument.java
@@ -20,10 +20,7 @@ package com.maddyhome.idea.vim.ui;
 
 import org.jetbrains.annotations.NotNull;
 
-import javax.swing.text.AttributeSet;
-import javax.swing.text.BadLocationException;
-import javax.swing.text.Document;
-import javax.swing.text.PlainDocument;
+import javax.swing.text.*;
 
 /**
  * This document provides insert/overwrite mode
@@ -32,7 +29,7 @@ public class ExDocument extends PlainDocument {
   /**
    * Toggles the insert/overwrite state
    */
-  public void toggleInsertReplace() {
+  void toggleInsertReplace() {
     overwrite = !overwrite;
   }
 
@@ -72,5 +69,19 @@ public class ExDocument extends PlainDocument {
     }
   }
 
-  protected boolean overwrite = false;
+  public char getCharacter(int offset) {
+    // If we're a proportional font, 'o' is a good char to use. If we're fixed width, it's still a good char to use
+    if (offset >= getLength())
+      return 'o';
+
+    try {
+      final Segment segment = new Segment();
+      getContent().getChars(offset,1, segment);
+      return segment.charAt(0);
+    } catch (BadLocationException e) {
+      return 'o';
+    }
+  }
+
+  private boolean overwrite = false;
 }
diff --git a/src/com/maddyhome/idea/vim/ui/ExEditorKit.java b/src/com/maddyhome/idea/vim/ui/ExEditorKit.java
index 049aa3c70..ed691b42a 100644
--- a/src/com/maddyhome/idea/vim/ui/ExEditorKit.java
+++ b/src/com/maddyhome/idea/vim/ui/ExEditorKit.java
@@ -81,13 +81,13 @@ public class ExEditorKit extends DefaultEditorKit {
   }
 
   @Nullable
-  public static KeyStroke convert(@NotNull ActionEvent event) {
+  private static KeyStroke convert(@NotNull ActionEvent event) {
     String cmd = event.getActionCommand();
     int mods = event.getModifiers();
     if (cmd != null && cmd.length() > 0) {
       char ch = cmd.charAt(0);
       if (ch < ' ') {
-        if (mods == KeyEvent.CTRL_MASK) {
+        if ((mods & KeyEvent.CTRL_MASK) != 0) {
           return KeyStroke.getKeyStroke(KeyEvent.VK_A + ch - 1, mods);
         }
       }
@@ -99,25 +99,22 @@ public class ExEditorKit extends DefaultEditorKit {
     return null;
   }
 
-  public static final String DefaultExKey = "default-ex-key";
-  public static final String CancelEntry = "cancel-entry";
-  public static final String CompleteEntry = "complete-entry";
-  public static final String EscapeChar = "escape";
-  public static final String DeletePreviousChar = "delete-prev-char";
-  public static final String DeletePreviousWord = "delete-prev-word";
-  public static final String DeleteToCursor = "delete-to-cursor";
-  public static final String DeleteFromCursor = "delete-from-cursor";
-  public static final String ToggleInsertReplace = "toggle-insert";
-  public static final String InsertRegister = "insert-register";
-  public static final String InsertWord = "insert-word";
-  public static final String InsertWORD = "insert-WORD";
-  public static final String HistoryUp = "history-up";
-  public static final String HistoryDown = "history-down";
-  public static final String HistoryUpFilter = "history-up-filter";
-  public static final String HistoryDownFilter = "history-down-filter";
-  public static final String StartDigraph = "start-digraph";
+  static final String CancelEntry = "cancel-entry";
+  static final String CompleteEntry = "complete-entry";
+  static final String EscapeChar = "escape";
+  static final String DeletePreviousChar = "delete-prev-char";
+  static final String DeletePreviousWord = "delete-prev-word";
+  static final String DeleteToCursor = "delete-to-cursor";
+  static final String DeleteFromCursor = "delete-from-cursor";
+  static final String ToggleInsertReplace = "toggle-insert";
+  static final String InsertRegister = "insert-register";
+  static final String HistoryUp = "history-up";
+  static final String HistoryDown = "history-down";
+  static final String HistoryUpFilter = "history-up-filter";
+  static final String HistoryDownFilter = "history-down-filter";
+  static final String StartDigraph = "start-digraph";
 
-  @NotNull protected final Action[] exActions = new Action[]{
+  @NotNull private final Action[] exActions = new Action[]{
     new ExEditorKit.CancelEntryAction(),
     new ExEditorKit.CompleteEntryAction(),
     new ExEditorKit.EscapeCharAction(),
@@ -160,8 +157,12 @@ public class ExEditorKit extends DefaultEditorKit {
     }
   }
 
+  public interface MultiStepAction extends Action {
+    void reset();
+  }
+
   public static class HistoryUpAction extends TextAction {
-    public HistoryUpAction() {
+    HistoryUpAction() {
       super(HistoryUp);
     }
 
@@ -172,7 +173,7 @@ public class ExEditorKit extends DefaultEditorKit {
   }
 
   public static class HistoryDownAction extends TextAction {
-    public HistoryDownAction() {
+    HistoryDownAction() {
       super(HistoryDown);
     }
 
@@ -183,7 +184,7 @@ public class ExEditorKit extends DefaultEditorKit {
   }
 
   public static class HistoryUpFilterAction extends TextAction {
-    public HistoryUpFilterAction() {
+    HistoryUpFilterAction() {
       super(HistoryUpFilter);
     }
 
@@ -194,7 +195,7 @@ public class ExEditorKit extends DefaultEditorKit {
   }
 
   public static class HistoryDownFilterAction extends TextAction {
-    public HistoryDownFilterAction() {
+    HistoryDownFilterAction() {
       super(HistoryDownFilter);
     }
 
@@ -204,15 +205,15 @@ public class ExEditorKit extends DefaultEditorKit {
     }
   }
 
-  public static class InsertRegisterAction extends TextAction {
-    private static enum State {
+  public static class InsertRegisterAction extends TextAction implements MultiStepAction {
+    private enum State {
       SKIP_CTRL_R,
       WAIT_REGISTER,
     }
 
     @NotNull private State state = State.SKIP_CTRL_R;
 
-    public InsertRegisterAction() {
+    InsertRegisterAction() {
       super(InsertRegister);
     }
 
@@ -223,11 +224,12 @@ public class ExEditorKit extends DefaultEditorKit {
         switch (state) {
           case SKIP_CTRL_R:
             state = State.WAIT_REGISTER;
-            target.setCurrentAction(this);
+            target.setCurrentAction(this, '\"');
             break;
+
           case WAIT_REGISTER:
             state = State.SKIP_CTRL_R;
-            target.setCurrentAction(null);
+            target.clearCurrentAction();
             final char c = key.getKeyChar();
             if (c != KeyEvent.CHAR_UNDEFINED) {
               final Register register = VimPlugin.getRegister().getRegister(c);
@@ -235,20 +237,27 @@ public class ExEditorKit extends DefaultEditorKit {
                 final String oldText = target.getText();
                 final String text = register.getText();
                 if (oldText != null && text != null) {
-                  target.setText(oldText + text);
+                  final int offset = target.getCaretPosition();
+                  target.setText(oldText.substring(0, offset) + text + oldText.substring(offset));
+                  target.setCaretPosition(offset + text.length());
                 }
               }
-            }
-            else {
+            } else if ((key.getModifiers() & KeyEvent.CTRL_MASK) != 0 && key.getKeyCode() == KeyEvent.VK_C) {
+              // Eat any unused keys, unless it's <C-C>, in which case forward on and cancel entry
               target.handleKey(key);
             }
         }
       }
     }
+
+    @Override
+    public void reset() {
+      state = State.SKIP_CTRL_R;
+    }
   }
 
   public static class CompleteEntryAction extends TextAction {
-    public CompleteEntryAction() {
+    CompleteEntryAction() {
       super(CompleteEntry);
     }
 
@@ -256,27 +265,29 @@ public class ExEditorKit extends DefaultEditorKit {
       logger.debug("complete entry");
       KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
 
-      KeyHandler.getInstance().handleKey(
-        ExEntryPanel.getInstance().getEntry().getEditor(),
-        stroke,
-        ExEntryPanel.getInstance().getEntry().getContext());
+      // We send the <Enter> keystroke through the key handler rather than calling ProcessGroup#processExEntry directly.
+      // We do this for a couple of reasons:
+      // * The C mode mapping for ProcessExEntryAction handles the actual entry, and most importantly, it does so as a
+      //   write action
+      // * The key handler routines get the chance to clean up and reset state
+      final ExTextField entry = ExEntryPanel.getInstance().getEntry();
+      KeyHandler.getInstance().handleKey(entry.getEditor(), stroke, entry.getContext());
     }
   }
 
   public static class CancelEntryAction extends TextAction {
-    public CancelEntryAction() {
+    CancelEntryAction() {
       super(CancelEntry);
     }
 
     public void actionPerformed(ActionEvent e) {
-      VimPlugin.getProcess().cancelExEntry(
-        ExEntryPanel.getInstance().getEntry().getEditor(),
-        ExEntryPanel.getInstance().getEntry().getContext());
+      ExTextField target = (ExTextField)getTextComponent(e);
+      target.cancel();
     }
   }
 
   public static class EscapeCharAction extends TextAction {
-    public EscapeCharAction() {
+    EscapeCharAction() {
       super(EscapeChar);
     }
 
@@ -287,7 +298,7 @@ public class ExEditorKit extends DefaultEditorKit {
   }
 
   public static class DeletePreviousCharAction extends TextAction {
-    public DeletePreviousCharAction() {
+    DeletePreviousCharAction() {
       super(DeletePreviousChar);
     }
 
@@ -323,9 +334,7 @@ public class ExEditorKit extends DefaultEditorKit {
           doc.remove(dot - delChars, delChars);
         }
         else {
-          VimPlugin.getProcess().cancelExEntry(
-            ExEntryPanel.getInstance().getEntry().getEditor(),
-            ExEntryPanel.getInstance().getEntry().getContext());
+          target.cancel();
         }
       }
       catch (BadLocationException bl) {
@@ -335,7 +344,7 @@ public class ExEditorKit extends DefaultEditorKit {
   }
 
   public static class DeletePreviousWordAction extends TextAction {
-    public DeletePreviousWordAction() {
+    DeletePreviousWordAction() {
       super(DeletePreviousWord);
     }
 
@@ -362,7 +371,7 @@ public class ExEditorKit extends DefaultEditorKit {
   }
 
   public static class DeleteToCursorAction extends TextAction {
-    public DeleteToCursorAction() {
+    DeleteToCursorAction() {
       super(DeleteToCursor);
     }
 
@@ -385,7 +394,7 @@ public class ExEditorKit extends DefaultEditorKit {
   }
 
   public static class DeleteFromCursorAction extends TextAction {
-    public DeleteFromCursorAction() {
+    DeleteFromCursorAction() {
       super(DeleteFromCursor);
     }
 
@@ -408,7 +417,7 @@ public class ExEditorKit extends DefaultEditorKit {
   }
 
   public static class ToggleInsertReplaceAction extends TextAction {
-    public ToggleInsertReplaceAction() {
+    ToggleInsertReplaceAction() {
       super(ToggleInsertReplace);
 
       logger.debug("ToggleInsertReplaceAction()");
@@ -424,10 +433,10 @@ public class ExEditorKit extends DefaultEditorKit {
     }
   }
 
-  public static class StartDigraphAction extends TextAction {
+  public static class StartDigraphAction extends TextAction implements MultiStepAction {
     @Nullable private DigraphSequence digraphSequence;
 
-    public StartDigraphAction() {
+    StartDigraphAction() {
       super(StartDigraph);
     }
 
@@ -437,14 +446,23 @@ public class ExEditorKit extends DefaultEditorKit {
       if (key != null && digraphSequence != null) {
         DigraphSequence.DigraphResult res = digraphSequence.processKey(key, target.getEditor());
         switch (res.getResult()) {
-          case DigraphSequence.DigraphResult.RES_BAD:
-            target.setCurrentAction(null);
-            target.handleKey(key);
+          case DigraphSequence.DigraphResult.RES_OK:
+            target.setCurrentActionPromptCharacter(res.getPromptCharacter());
             break;
+
+          case DigraphSequence.DigraphResult.RES_BAD:
+            target.clearCurrentAction();
+            // Eat the character, unless it's <C-C>, in which case, forward on and cancel entry. Note that at some point
+            // we should support input of control characters
+            if ((key.getModifiers() & KeyEvent.CTRL_MASK) != 0 && key.getKeyCode() == KeyEvent.VK_C) {
+              target.handleKey(key);
+            }
+            break;
+
           case DigraphSequence.DigraphResult.RES_DONE:
             final KeyStroke digraph = res.getStroke();
             digraphSequence = null;
-            target.setCurrentAction(null);
+            target.clearCurrentAction();
             if (digraph != null) {
               target.handleKey(digraph);
             }
@@ -452,11 +470,16 @@ public class ExEditorKit extends DefaultEditorKit {
         }
       }
       else if (key != null && DigraphSequence.isDigraphStart(key)) {
-        target.setCurrentAction(this);
         digraphSequence = new DigraphSequence();
-        digraphSequence.processKey(key, target.getEditor());
+        DigraphSequence.DigraphResult res = digraphSequence.processKey(key, target.getEditor());
+        target.setCurrentAction(this, res.getPromptCharacter());
       }
     }
+
+    @Override
+    public void reset() {
+      digraphSequence = null;
+    }
   }
 
   private static ExEditorKit instance;
diff --git a/src/com/maddyhome/idea/vim/ui/ExEntryPanel.java b/src/com/maddyhome/idea/vim/ui/ExEntryPanel.java
index 06102ee2e..c7e45daa3 100644
--- a/src/com/maddyhome/idea/vim/ui/ExEntryPanel.java
+++ b/src/com/maddyhome/idea/vim/ui/ExEntryPanel.java
@@ -79,6 +79,8 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
       }
     };
 
+    new ExShortcutKeyAction(this).registerCustomShortcutSet();
+
     LafManager.getInstance().addLafManagerListener(this);
 
     updateUI();
@@ -93,6 +95,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
   private void setFontForElements() {
     final Font font = UiHelper.getEditorFont();
     label.setFont(font);
+    entry.setFont(font);
   }
 
   /**
@@ -105,11 +108,11 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
    * @param count    A holder for the ex entry count
    */
   public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) {
-    entry.setEditor(editor, context);
     this.label.setText(label);
     this.count = count;
     setFontForElements();
-    entry.setDocument(entry.createDefaultModel());
+    entry.reset();
+    entry.setEditor(editor, context);
     entry.setText(initText);
     entry.setType(label);
     parent = editor.getContentComponent();
@@ -139,7 +142,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
   public void updateUI() {
     super.updateUI();
 
-    setBorder(BorderFactory.createEtchedBorder());
+    setBorder(new ExPanelBorder());
 
     // Can be null when called from base constructor
     //noinspection ConstantConditions
@@ -229,6 +232,8 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
     logger.info("deactivate");
     if (!active) return;
     active = false;
+    entry.deactivate();
+
     if (!ApplicationManager.getApplication().isUnitTestMode()) {
       if (refocusOwningEditor && parent != null) {
         UiHelper.requestFocus(parent);
@@ -279,7 +284,7 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
 
   @NotNull private final DocumentListener documentListener = new DocumentAdapter() {
     @Override
-    protected void textChanged(DocumentEvent e) {
+    protected void textChanged(@NotNull DocumentEvent e) {
       final Editor editor = entry.getEditor();
       final boolean forwards = !label.getText().equals("?");
       if (incHighlighter != null) {
@@ -300,6 +305,5 @@ public class ExEntryPanel extends JPanel implements LafManagerListener {
   private boolean active;
 
   private static ExEntryPanel instance;
-
   private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
 }
diff --git a/src/com/maddyhome/idea/vim/ui/ExKeyBindings.java b/src/com/maddyhome/idea/vim/ui/ExKeyBindings.java
index 47f41c122..efd9697b9 100644
--- a/src/com/maddyhome/idea/vim/ui/ExKeyBindings.java
+++ b/src/com/maddyhome/idea/vim/ui/ExKeyBindings.java
@@ -24,20 +24,20 @@ import javax.swing.*;
 import javax.swing.text.JTextComponent.KeyBinding;
 import java.awt.event.KeyEvent;
 
-/**
- *
- */
 public class ExKeyBindings {
   @NotNull
-  public static KeyBinding[] getBindings() {
+  static KeyBinding[] getBindings() {
     return bindings;
   }
 
   // TODO - add the following keys:
   // Ctrl-\ Ctrl-N - abort
   static final KeyBinding[] bindings = new KeyBinding[]{
+    // Note that escape will cancel a pending insert digraph/register before cancelling
     new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ExEditorKit.EscapeChar),
     new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, KeyEvent.CTRL_MASK), ExEditorKit.EscapeChar),
+
+    // Cancel immediately cancels entry
     new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK), ExEditorKit.CancelEntry),
 
     new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ExEditorKit.CompleteEntry),
@@ -82,9 +82,10 @@ public class ExKeyBindings {
     new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK), ExEditorKit.StartDigraph),
     new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK), ExEditorKit.StartDigraph),
 
+    new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_MASK), ExEditorKit.InsertRegister),
+
+    // These appear to be non-Vim shortcuts
     new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_MASK), ExEditorKit.pasteAction),
     new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.SHIFT_MASK), ExEditorKit.pasteAction),
-
-    new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_MASK), ExEditorKit.InsertRegister),
   };
 }
diff --git a/src/com/maddyhome/idea/vim/ui/ExOutputPanel.java b/src/com/maddyhome/idea/vim/ui/ExOutputPanel.java
index 374467f31..4ffc999e3 100644
--- a/src/com/maddyhome/idea/vim/ui/ExOutputPanel.java
+++ b/src/com/maddyhome/idea/vim/ui/ExOutputPanel.java
@@ -113,7 +113,7 @@ public class ExOutputPanel extends JPanel implements LafManagerListener {
   public void updateUI() {
     super.updateUI();
 
-    setBorder(BorderFactory.createEtchedBorder());
+    setBorder(new ExPanelBorder());
 
     // Can be null when called from base constructor
     //noinspection ConstantConditions
diff --git a/src/com/maddyhome/idea/vim/ui/ExPanelBorder.kt b/src/com/maddyhome/idea/vim/ui/ExPanelBorder.kt
new file mode 100644
index 000000000..20711450f
--- /dev/null
+++ b/src/com/maddyhome/idea/vim/ui/ExPanelBorder.kt
@@ -0,0 +1,22 @@
+package com.maddyhome.idea.vim.ui
+
+import com.intellij.ui.JBColor
+import com.intellij.ui.SideBorder
+import com.intellij.util.ui.JBInsets
+
+import java.awt.*
+
+class ExPanelBorder internal constructor() : SideBorder(JBColor.border(), SideBorder.TOP) {
+
+    override fun getBorderInsets(component: Component?): Insets {
+        return JBInsets(getThickness() + 2, 0, 2, 2)
+    }
+
+    override fun getBorderInsets(component: Component?, insets: Insets): Insets {
+        insets.top = getThickness() + 2
+        insets.left = 0
+        insets.bottom = 2
+        insets.right = 2
+        return insets
+    }
+}
diff --git a/src/com/maddyhome/idea/vim/ui/ExShortcutKeyAction.kt b/src/com/maddyhome/idea/vim/ui/ExShortcutKeyAction.kt
new file mode 100644
index 000000000..f0e402211
--- /dev/null
+++ b/src/com/maddyhome/idea/vim/ui/ExShortcutKeyAction.kt
@@ -0,0 +1,50 @@
+package com.maddyhome.idea.vim.ui
+
+import com.intellij.openapi.actionSystem.*
+import com.maddyhome.idea.vim.VimPlugin
+import java.awt.event.KeyEvent
+import javax.swing.KeyStroke
+
+/**
+ * An IntelliJ action to forward shortcuts to the ex entry component
+ * <p>
+ * Key events are processed by the IDE action system before they are dispatched to the actual component, which means
+ * they take precedence over the keyboard shortcuts registered with the ex component as Swing actions. This can cause
+ * clashes such as <C-R> invoking the Run action instead of the Paste Register ex action, or <BS> being handled by the
+ * editor rather than allowing us to cancel the ex entry.
+ * <p>
+ * This class is an IDE action that is registered with the ex entry component, so is available only when the ex entry
+ * component has focus. It registers all shortcuts used by the Swing actions and forwards them directly to the key
+ * handler.
+ */
+class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : AnAction() {
+
+    override fun actionPerformed(e: AnActionEvent) {
+        val keyStroke = getKeyStroke(e)
+        if (keyStroke != null) {
+            VimPlugin.getProcess().processExKey(exEntryPanel.entry.editor, keyStroke)
+        }
+    }
+
+    override fun update(e: AnActionEvent) {
+        e.presentation.isEnabled = exEntryPanel.isActive
+    }
+
+    private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
+        val inputEvent = e.inputEvent
+        if (inputEvent is KeyEvent) {
+            return KeyStroke.getKeyStrokeForEvent(inputEvent)
+        }
+        return null
+    }
+
+    fun registerCustomShortcutSet() {
+
+        val shortcuts = ExKeyBindings.bindings.map {
+            KeyboardShortcut(it.key, null)
+        }.toTypedArray()
+
+        registerCustomShortcutSet({ shortcuts }, exEntryPanel)
+    }
+}
+
diff --git a/src/com/maddyhome/idea/vim/ui/ExTextField.java b/src/com/maddyhome/idea/vim/ui/ExTextField.java
index 2182fe271..fe554c68a 100644
--- a/src/com/maddyhome/idea/vim/ui/ExTextField.java
+++ b/src/com/maddyhome/idea/vim/ui/ExTextField.java
@@ -21,48 +21,62 @@ package com.maddyhome.idea.vim.ui;
 import com.intellij.openapi.actionSystem.DataContext;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.colors.EditorColorsManager;
-import com.intellij.openapi.editor.colors.EditorFontType;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.util.ui.JBUI;
 import com.maddyhome.idea.vim.VimPlugin;
 import com.maddyhome.idea.vim.group.HistoryGroup;
+import kotlin.text.StringsKt;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
 
 import javax.swing.*;
-import javax.swing.text.Document;
-import javax.swing.text.Keymap;
+import javax.swing.plaf.basic.BasicTextFieldUI;
+import javax.swing.text.*;
 import java.awt.*;
-import java.awt.event.FocusEvent;
-import java.awt.event.FocusListener;
-import java.awt.event.KeyEvent;
+import java.awt.event.*;
 import java.util.Date;
 import java.util.List;
 
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
 /**
  * Provides a custom keymap for the text field. The keymap is the VIM Ex command keymapping
  */
 public class ExTextField extends JTextField {
 
   ExTextField() {
-    addFocusListener(new FocusListener() {
-      @Override
-      public void focusGained(FocusEvent e) {
-        setCaretPosition(getText().length());
-      }
+    CommandLineCaret caret = new CommandLineCaret();
+    caret.setBlinkRate(getCaret().getBlinkRate());
+    setCaret(caret);
+    setNormalModeCaret();
 
+    addCaretListener(e -> resetCaret());
+    addMouseListener(new MouseAdapter() {
       @Override
-      public void focusLost(FocusEvent e) {
+      public void mouseClicked(MouseEvent e) {
+        // If we're in the middle of an action (e.g. entering a register to paste, or inserting a digraph), cancel it if
+        // the mouse is clicked anywhere. Vim's behaviour is to use the mouse click as an event, which can lead to
+        // something like : !%!C, which I don't believe is documented, or useful
+        if (currentAction != null) {
+          clearCurrentAction();
+        }
+        super.mouseClicked(e);
       }
     });
   }
 
-  // Minimize margins and insets. These get added to the default margins in the UI class that we can't override.
-  // (I.e. DarculaTextFieldUI#getDefaultMargins, MacIntelliJTextFieldUI#getDefaultMargin, WinIntelliJTextFieldUI#getDefaultMargin)
-  // This is an attempt to mitigate the gap in ExEntryPanel between the label (':', '/', '?') and the text field.
-  // See VIM-1485
+  void reset() {
+    clearCurrentAction();
+    setInsertMode();
+  }
+
+  void deactivate() {
+    clearCurrentAction();
+  }
+
   @Override
   public Insets getMargin() {
     return JBUI.emptyInsets();
@@ -76,10 +90,11 @@ public class ExTextField extends JTextField {
   // Called when the LAF is changed, but only if the control is visible
   @Override
   public void updateUI() {
-    super.updateUI();
-
-    Font font = EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.PLAIN);
-    setFont(font);
+    // Override the default look and feel specific UI so we can have a completely borderless and margin-less text field.
+    // (See TextFieldWithPopupHandlerUI#getDefaultMargins and derived classes). This allows us to draw the text field
+    // directly next to the label
+    setUI(new BasicTextFieldUI());
+    invalidate();
 
     setBorder(null);
 
@@ -98,7 +113,7 @@ public class ExTextField extends JTextField {
     setKeymap(map);
   }
 
-  public void setType(@NotNull String type) {
+  void setType(@NotNull String type) {
     String hkey = null;
     switch (type.charAt(0)) {
       case '/':
@@ -116,11 +131,11 @@ public class ExTextField extends JTextField {
     }
   }
 
-  public void saveLastEntry() {
+  void saveLastEntry() {
     lastEntry = getText();
   }
 
-  public void selectHistory(boolean isUp, boolean filter) {
+  void selectHistory(boolean isUp, boolean filter) {
     int dir = isUp ? -1 : 1;
     if (histIndex + dir < 0 || histIndex + dir > history.size()) {
       VimPlugin.indicateError();
@@ -164,7 +179,28 @@ public class ExTextField extends JTextField {
     }
   }
 
-  private static final String vimExTextFieldDisposeKey = "vimExTextFieldDisposeKey";
+  private void updateText(String string) {
+    super.setText(string);
+  }
+
+  public void setText(String string) {
+    super.setText(string);
+
+    saveLastEntry();
+  }
+
+  void setEditor(Editor editor, DataContext context) {
+    this.editor = editor;
+    this.context = context;
+    String disposeKey = vimExTextFieldDisposeKey + editor.hashCode();
+    Project project = editor.getProject();
+    if (Disposer.get(disposeKey) == null && project != null) {
+      Disposer.register(project, () -> {
+        this.editor = null;
+        this.context = null;
+      }, disposeKey);
+    }
+  }
 
   public Editor getEditor() {
     return editor;
@@ -186,21 +222,25 @@ public class ExTextField extends JTextField {
         c = Character.toChars(codePoint)[0];
       }
     }
-    KeyEvent event = new KeyEvent(this, keyChar != KeyEvent.CHAR_UNDEFINED ? KeyEvent.KEY_TYPED :
-                                        (stroke.isOnKeyRelease() ? KeyEvent.KEY_RELEASED : KeyEvent.KEY_PRESSED),
-                                  (new Date()).getTime(), modifiers, keyCode, c);
 
-    super.processKeyEvent(event);
-  }
+    // Make sure the current action sees any subsequent keystrokes, and they're not processed by Swing's action system.
+    // Note that this will only handle simple characters and any control characters that are already registered against
+    // ExShortcutKeyAction - any other control characters will can be "stolen" by other IDE actions.
+    // If we need to capture ANY subsequent keystroke (e.g. for ^V<Tab>, or to stop the Swing standard <C-A> going to
+    // start of line), we should replace ExShortcutAction with a dispatcher registered with IdeEventQueue#addDispatcher.
+    // This gets called for ALL events, before the IDE starts to process key events for the action system. We can add a
+    // dispatcher that checks that the plugin is enabled, checks that the component with the focus is ExTextField,
+    // dispatch to ExEntryPanel#handleKey and if it's processed, mark the event as consumed.
+    if (currentAction != null) {
+      currentAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "" + c, modifiers));
+    }
+    else {
+      KeyEvent event = new KeyEvent(this, keyChar != KeyEvent.CHAR_UNDEFINED ? KeyEvent.KEY_TYPED :
+        (stroke.isOnKeyRelease() ? KeyEvent.KEY_RELEASED : KeyEvent.KEY_PRESSED),
+        (new Date()).getTime(), modifiers, keyCode, c);
 
-  public void updateText(String string) {
-    super.setText(string);
-  }
-
-  public void setText(String string) {
-    super.setText(string);
-
-    saveLastEntry();
+      super.processKeyEvent(event);
+    }
   }
 
   protected void processKeyEvent(KeyEvent e) {
@@ -220,182 +260,270 @@ public class ExTextField extends JTextField {
     return new ExDocument();
   }
 
-  public void escape() {
+  /**
+   * Cancels current action, if there is one. If not, cancels entry.
+   */
+  void escape() {
     if (currentAction != null) {
-      currentAction = null;
+      clearCurrentAction();
     }
     else {
-      VimPlugin.getProcess().cancelExEntry(editor, context);
+      cancel();
     }
   }
 
-  public void setCurrentAction(@Nullable Action action) {
+  /**
+   * Cancels entry, including any current action.
+   */
+  void cancel() {
+    clearCurrentAction();
+    VimPlugin.getProcess().cancelExEntry(editor, context);
+  }
+
+  void setCurrentAction(@NotNull ExEditorKit.MultiStepAction action, char pendingIndicator) {
     this.currentAction = action;
+    setCurrentActionPromptCharacter(pendingIndicator);
+  }
+
+  void clearCurrentAction() {
+    if (currentAction != null) {
+      currentAction.reset();
+    }
+    currentAction = null;
+    clearCurrentActionPromptCharacter();
+  }
+
+  void setCurrentActionPromptCharacter(char promptCharacter) {
+    final String text = removePromptCharacter();
+    this.currentActionPromptCharacter = promptCharacter;
+    currentActionPromptCharacterOffset = currentActionPromptCharacterOffset == -1 ? getCaretPosition() : currentActionPromptCharacterOffset;
+    StringBuilder sb = new StringBuilder(text);
+    sb.insert(currentActionPromptCharacterOffset, currentActionPromptCharacter);
+    updateText(sb.toString());
+    setCaretPosition(currentActionPromptCharacterOffset);
+  }
+
+  private void clearCurrentActionPromptCharacter() {
+    final int offset = getCaretPosition();
+    final String text = removePromptCharacter();
+    updateText(text);
+    setCaretPosition(min(offset, text.length()));
+    currentActionPromptCharacter = '\0';
+    currentActionPromptCharacterOffset = -1;
+  }
+
+  private String removePromptCharacter() {
+    return currentActionPromptCharacterOffset == -1
+      ? getText()
+      : StringsKt.removeRange(getText(), currentActionPromptCharacterOffset, currentActionPromptCharacterOffset + 1).toString();
   }
 
   @Nullable
-  public Action getCurrentAction() {
+  Action getCurrentAction() {
     return currentAction;
   }
 
-  public void toggleInsertReplace() {
+  private void setInsertMode() {
+    ExDocument doc = (ExDocument)getDocument();
+    if (doc.isOverwrite()) {
+      doc.toggleInsertReplace();
+    }
+    resetCaret();
+  }
+
+  void toggleInsertReplace() {
     ExDocument doc = (ExDocument)getDocument();
     doc.toggleInsertReplace();
-
-    /*
-    Caret caret;
-    int width;
-    if (doc.isOverwrite())
-    {
-        caret = blockCaret;
-        width = 8;
-    }
-    else
-    {
-        caret = origCaret;
-        width = 1;
-    }
-
-    setCaret(caret);
-    putClientProperty("caretWidth", new Integer(width));
-    */
+    resetCaret();
   }
 
-  /*
-  private static class BlockCaret extends DefaultCaret
-  {
-      public void paint(Graphics g)
-      {
-          if(!isVisible())
-              return;
-
-          try
-          {
-              Rectangle rectangle;
-              TextUI textui = getComponent().getUI();
-              rectangle = textui.modelToView(getComponent(), getDot(), Position.Bias.Forward);
-              if (rectangle == null || rectangle.width == 0 && rectangle.height == 0)
-              {
-                  return;
-              }
-              if (width > 0 && height > 0 && !_contains(rectangle.x, rectangle.y, rectangle.width, rectangle.height))
-              {
-                  Rectangle rectangle1 = g.getClipBounds();
-                  if (rectangle1 != null && !rectangle1.contains(this))
-                  {
-                      repaint();
-                  }
-                  damage(rectangle);
-              }
-              g.setColor(getComponent().getCaretColor());
-              int i = 8;
-              //rectangle.x -= i >> 1;
-              g.fillRect(rectangle.x, rectangle.y, i, rectangle.height - 1);
-              Document document = getComponent().getDocument();
-              if (document instanceof AbstractDocument)
-              {
-                  Element element = ((AbstractDocument)document).getBidiRootElement();
-                  if (element != null && element.getElementCount() > 1)
-                  {
-                      int[] flagXPoints = new int[3];
-                      int[] flagYPoints = new int[3];
-                      flagXPoints[0] = rectangle.x + i;
-                      flagYPoints[0] = rectangle.y;
-                      flagXPoints[1] = flagXPoints[0];
-                      flagYPoints[1] = flagYPoints[0] + 4;
-                      flagXPoints[2] = flagXPoints[0] + 4;
-                      flagYPoints[2] = flagYPoints[0];
-                      g.fillPolygon(flagXPoints, flagYPoints, 3);
-                  }
-              }
-          }
-          catch (BadLocationException badlocationexception)
-          {
-              // ignore
-          }
+  private void resetCaret() {
+    if (getCaretPosition() == getText().length() || currentActionPromptCharacterOffset == getText().length() - 1) {
+      setNormalModeCaret();
+    }
+    else {
+      ExDocument doc = (ExDocument)getDocument();
+      if (doc.isOverwrite()) {
+        setReplaceModeCaret();
       }
-
-      private boolean _contains(int i, int j, int k, int l)
-      {
-          int i1 = width;
-          int j1 = height;
-          if ((i1 | j1 | k | l) < 0)
-          {
-              return false;
-          }
-          int k1 = x;
-          int l1 = y;
-          if (i < k1 || j < l1)
-          {
-              return false;
-          }
-          if (k > 0)
-          {
-              i1 += k1;
-              k += i;
-              if (k <= i)
-              {
-                  if (i1 >= k1 || k > i1)
-                  {
-                      return false;
-                  }
-              }
-              else if (i1 >= k1 && k > i1)
-              {
-                  return false;
-              }
-          }
-          else if (k1 + i1 < i)
-          {
-              return false;
-          }
-          if (l > 0)
-          {
-              j1 += l1;
-              l += j;
-              if (l <= j)
-              {
-                  if (j1 >= l1 || l > j1)
-                  {
-                      return false;
-                  }
-              }
-              else if (j1 >= l1 && l > j1)
-              {
-                  return false;
-              }
-          }
-          else if (l1 + j1 < j)
-          {
-              return false;
-          }
-          return true;
+      else {
+        setInsertModeCaret();
       }
+    }
+  }
+
+  // The default cursor shapes for command line are:
+  // 'c' command-line normal is block
+  // 'ci' command-line insert is ver25
+  // 'cr' command-line replace is hor20
+  // see :help 'guicursor'
+  // Note that we can't easily support guicursor because we don't have arbitrary control over the IntelliJ editor caret
+  private void setNormalModeCaret() {
+    CommandLineCaret caret = (CommandLineCaret) getCaret();
+    caret.setBlockMode();
+  }
+
+  private void setInsertModeCaret() {
+    CommandLineCaret caret = (CommandLineCaret) getCaret();
+    caret.setMode(CommandLineCaret.CaretMode.VER, 25);
+  }
+
+  private void setReplaceModeCaret() {
+    CommandLineCaret caret = (CommandLineCaret) getCaret();
+    caret.setMode(CommandLineCaret.CaretMode.HOR, 20);
+  }
+
+  private static class CommandLineCaret extends DefaultCaret {
+
+    private CaretMode mode;
+    private int blockPercentage = 100;
+    private int lastBlinkRate = 0;
+    private boolean hasFocus;
+
+    public enum CaretMode {
+      BLOCK,
+      VER,
+      HOR
+    }
+
+    void setBlockMode() {
+      setMode(CaretMode.BLOCK, 100);
+    }
+
+    void setMode(CaretMode mode, int blockPercentage) {
+      if (this.mode == mode && this.blockPercentage == blockPercentage) {
+        return;
+      }
+
+      // Hide the current caret and redraw without it. Then make the new caret visible, but only if it was already
+      // (logically) visible/active. Always making it visible can start the flasher timer unnecessarily.
+      final boolean active = isActive();
+      if (isVisible()) {
+        setVisible(false);
+      }
+      this.mode = mode;
+      this.blockPercentage = blockPercentage;
+      if (active) {
+        setVisible(true);
+      }
+    }
+
+    private void updateDamage() {
+      try {
+        Rectangle r = getComponent().getUI().modelToView(getComponent(), getDot(), getDotBias());
+        damage(r);
+      }
+      catch(BadLocationException e) {
+        // ignore
+      }
+    }
+
+    @Override
+    public void focusGained(FocusEvent e) {
+      if (lastBlinkRate != 0) {
+        setBlinkRate(lastBlinkRate);
+        lastBlinkRate = 0;
+      }
+      super.focusGained(e);
+      updateDamage();
+      hasFocus = true;
+    }
+
+    @Override
+    public void focusLost(FocusEvent e) {
+      hasFocus = false;
+      lastBlinkRate = getBlinkRate();
+      setBlinkRate(0);
+      // We might be losing focus while the cursor is flashing, and is currently not visible
+      setVisible(true);
+      updateDamage();
+    }
+
+    @Override
+    public void paint(Graphics g) {
+      if (!isVisible()) return;
+
+      try {
+        final JTextComponent component = getComponent();
+        final Color color = g.getColor();
+
+        g.setColor(component.getBackground());
+        g.setXORMode(component.getCaretColor());
+
+        // We have to use the deprecated version because we still support 1.8
+        final Rectangle r = component.getUI().modelToView(component, getDot(), getDotBias());
+        FontMetrics fm = g.getFontMetrics();
+        final int boundsHeight = fm.getHeight();
+        if (!hasFocus) {
+          r.setBounds(r.x, r.y, getCaretWidth(fm, 100), boundsHeight);
+          g.drawRect(r.x, r.y, r.width, r.height);
+        }
+        else {
+          r.setBounds(r.x, r.y, getCaretWidth(fm, blockPercentage), getBlockHeight(boundsHeight));
+          g.fillRect(r.x, r.y + boundsHeight - r.height, r.width, r.height);
+        }
+        g.setPaintMode();
+        g.setColor(color);
+      }
+      catch (BadLocationException e) {
+        // ignore
+      }
+    }
+
+    protected synchronized void damage(Rectangle r) {
+      if (r != null) {
+        JTextComponent component = getComponent();
+        Font font = component.getFont();
+        FontMetrics fm = component.getFontMetrics(font);
+        final int blockHeight = fm.getHeight();
+        if (!hasFocus) {
+          width = this.getCaretWidth(fm, 100);
+          height = blockHeight;
+        }
+        else {
+          width = this.getCaretWidth(fm, blockPercentage);
+          height = getBlockHeight(blockHeight);
+        }
+        x = r.x;
+        y = r.y + blockHeight - height;
+        repaint();
+      }
+    }
+
+    private int getCaretWidth(FontMetrics fm, int widthPercentage) {
+      if (mode == CaretMode.VER) {
+        // Don't show a proportional width of a proportional font
+        final int fullWidth = fm.charWidth('o');
+        return max(1, fullWidth * widthPercentage / 100);
+      }
+
+      final char c = ((ExDocument)getComponent().getDocument()).getCharacter(getComponent().getCaretPosition());
+      return fm.charWidth(c);
+    }
+
+    private int getBlockHeight(int fullHeight) {
+      if (mode == CaretMode.HOR) {
+        return max(1, fullHeight * blockPercentage / 100);
+      }
+      return fullHeight;
+    }
+  }
+
+  @TestOnly
+  public String getCaretShape() {
+    CommandLineCaret caret = (CommandLineCaret) getCaret();
+    return String.format("%s %d", caret.mode, caret.blockPercentage);
   }
-  */
 
   private Editor editor;
   private DataContext context;
   private String lastEntry;
   private List<HistoryGroup.HistoryEntry> history;
   private int histIndex = 0;
-  @Nullable private Action currentAction;
-  // TODO - support block cursor for overwrite mode
-  //private Caret origCaret;
-  //private Caret blockCaret;
+  @Nullable private ExEditorKit.MultiStepAction currentAction;
+  private char currentActionPromptCharacter;
+  private int currentActionPromptCharacterOffset = -1;
 
+  private static final String vimExTextFieldDisposeKey = "vimExTextFieldDisposeKey";
   private static final Logger logger = Logger.getInstance(ExTextField.class.getName());
-
-  void setEditor(Editor editor, DataContext context) {
-    this.editor = editor;
-    this.context = context;
-    String disposeKey = vimExTextFieldDisposeKey + editor.hashCode();
-    Project project = editor.getProject();
-    if (Disposer.get(disposeKey) == null && project != null) {
-      Disposer.register(project, () -> {
-        this.editor = null;
-        this.context = null;
-      }, disposeKey);
-    }
-  }
 }
diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.java b/test/org/jetbrains/plugins/ideavim/VimTestCase.java
index c9a536591..bc1fd403c 100644
--- a/test/org/jetbrains/plugins/ideavim/VimTestCase.java
+++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.java
@@ -79,6 +79,9 @@ public abstract class VimTestCase extends UsefulTestCase {
     KeyHandler.getInstance().fullReset(myFixture.getEditor());
     Options.getInstance().resetAllOptions();
     VimPlugin.getKey().resetKeyMappings();
+
+    // Make sure the entry text field gets a bounds, or we won't be able to work out caret location
+    ExEntryPanel.getInstance().getEntry().setBounds(0,0, 100, 25);
   }
 
   protected String getTestDataPath() {
diff --git a/test/org/jetbrains/plugins/ideavim/action/MotionActionTest.java b/test/org/jetbrains/plugins/ideavim/action/MotionActionTest.java
index 7be86e5e3..b2a1bffc7 100644
--- a/test/org/jetbrains/plugins/ideavim/action/MotionActionTest.java
+++ b/test/org/jetbrains/plugins/ideavim/action/MotionActionTest.java
@@ -158,41 +158,6 @@ public class MotionActionTest extends VimTestCase {
     myFixture.checkResult(" 0:<caret>_ 1:a 2:b 3:c \n");
   }
 
-  // VIM-326 |d| |v_ib|
-  public void testDeleteInnerBlock() {
-    typeTextInFile(parseKeys("di)"),
-                   "foo(\"b<caret>ar\")\n");
-    myFixture.checkResult("foo()\n");
-  }
-
-  // VIM-326 |d| |v_ib|
-  public void testDeleteInnerBlockCaretBeforeString() {
-    typeTextInFile(parseKeys("di)"),
-                   "foo(<caret>\"bar\")\n");
-    myFixture.checkResult("foo()\n");
-  }
-
-  // VIM-326 |c| |v_ib|
-  public void testChangeInnerBlockCaretBeforeString() {
-    typeTextInFile(parseKeys("ci)"),
-                   "foo(<caret>\"bar\")\n");
-    myFixture.checkResult("foo()\n");
-  }
-
-  // VIM-392 |c| |v_ib|
-  public void testChangeInnerBlockCaretBeforeBlock() {
-    typeTextInFile(parseKeys("ci)"),
-                   "foo<caret>(bar)\n");
-    myFixture.checkResult("foo()\n");
-    assertOffset(4);
-  }
-
-  // |v_ib|
-  public void testInnerBlockCrashWhenNoDelimiterFound() {
-    typeTextInFile(parseKeys("di)"), "(x\n");
-    myFixture.checkResult("(x\n");
-  }
-
   // VIM-314 |d| |v_iB|
   public void testDeleteInnerCurlyBraceBlock() {
     typeTextInFile(parseKeys("di{"),
@@ -226,30 +191,6 @@ public class MotionActionTest extends VimTestCase {
     assertOffset(6);
   }
 
-  // VIM-275 |d| |v_ib|
-  public void testDeleteInnerParensBlockBeforeOpen() {
-    typeTextInFile(parseKeys("di)"),
-                   "foo<caret>(bar)\n");
-    myFixture.checkResult("foo()\n");
-    assertOffset(4);
-  }
-
-  // |d| |v_ib|
-  public void testDeleteInnerParensBlockBeforeClose() {
-    typeTextInFile(parseKeys("di)"),
-                   "foo(bar<caret>)\n");
-    myFixture.checkResult("foo()\n");
-  }
-
-  // |d| |v_ab|
-  public void testDeleteOuterBlock() {
-    typeTextInFile(parseKeys("da)"),
-                   "foo(b<caret>ar, baz);\n");
-    myFixture.checkResult("foo;\n");
-  }
-
-
-
   // |d| |v_aw|
   public void testDeleteOuterWord() {
     typeTextInFile(parseKeys("daw"),
@@ -449,350 +390,6 @@ public class MotionActionTest extends VimTestCase {
     myFixture.checkResult("foo = ;\n");
   }
 
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockCaretInHtml() {
-    typeTextInFile(parseKeys("dit"), "<template <caret>name=\"hello\">\n" +
-                                     "  <button>Click Me</button>\n" +
-                                     "  <p>You've pressed the button {{counter}} times.</p>\n" +
-                                     "</template>\n");
-    myFixture.checkResult("<template name=\"hello\"></template>\n");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockCaretInHtmlUnclosedTag() {
-    typeTextInFile(parseKeys("dit"), "<template <caret>name=\"hello\">\n" +
-                                     "  <button>Click Me</button>\n" +
-                                     "  <br>\n" +
-                                     "  <p>You've pressed the button {{counter}} times.</p>\n" +
-                                     "</template>\n");
-    myFixture.checkResult("<template name=\"hello\"></template>\n");
-  }
-
-  public void testDeleteInnerTagBlockCaretEdgeTag() {
-    typeTextInFile(parseKeys("dit"), "<template name=\"hello\"<caret>>\n" +
-                                     "  <button>Click Me</button>\n" +
-                                     "  <br>\n" +
-                                     "  <p>You've pressed the button {{counter}} times.</p>\n" +
-                                     "</template>\n");
-    myFixture.checkResult("<template name=\"hello\"></template>\n");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBefore() {
-    typeTextInFile(parseKeys("dit"), "abc<caret>de<tag>fg</tag>hi");
-    myFixture.checkResult("abcde<tag>fg</tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInOpen() {
-    typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fg</tag>hi");
-    myFixture.checkResult("abcde<tag></tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInOpenEndOfLine() {
-    typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fg</tag>");
-    myFixture.checkResult("abcde<tag></tag>");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInOpenStartOfLine() {
-    typeTextInFile(parseKeys("dit"), "<ta<caret>g>fg</tag>hi");
-    myFixture.checkResult("<tag></tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInOpenWithArgs() {
-    typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g name = \"name\">fg</tag>hi");
-    myFixture.checkResult("abcde<tag name = \"name\"></tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBetween() {
-    typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>g</tag>hi");
-    myFixture.checkResult("abcde<tag></tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBetweenTagWithRegex() {
-    typeTextInFile(parseKeys("dit"), "abcde<[abc]*>af<caret>gbc</[abc]*>hi");
-    myFixture.checkResult("abcde<[abc]*></[abc]*>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBetweenCamelCase() {
-    typeTextInFile(parseKeys("dit"), "abcde<tAg>f<caret>g</tag>hi");
-    myFixture.checkResult("abcde<tAg></tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBetweenCaps() {
-    typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>g</TAG>hi");
-    myFixture.checkResult("abcde<tag></TAG>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBetweenWithSpaceBeforeTag() {
-    typeTextInFile(parseKeys("dit"), "abcde< tag>f<caret>g</ tag>hi");
-    myFixture.checkResult("abcde< tag>fg</ tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBetweenWithSpaceAfterTag() {
-    typeTextInFile(parseKeys("dit"), "abcde<tag >f<caret>g</tag>hi");
-    myFixture.checkResult("abcde<tag ></tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBetweenWithArgs() {
-    typeTextInFile(parseKeys("dit"), "abcde<tag name = \"name\">f<caret>g</tag>hi");
-    myFixture.checkResult("abcde<tag name = \"name\"></tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInClose() {
-    typeTextInFile(parseKeys("dit"), "abcde<tag>fg</ta<caret>g>hi");
-    myFixture.checkResult("abcde<tag></tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockAfter() {
-    typeTextInFile(parseKeys("dit"), "abcde<tag>fg</tag>h<caret>i");
-    myFixture.checkResult("abcde<tag>fg</tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInAlone() {
-    typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fghi");
-    myFixture.checkResult("abcde<tag>fghi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockWithoutTags() {
-    typeTextInFile(parseKeys("dit"), "abc<caret>de");
-    myFixture.checkResult("abcde");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBeforeWithoutOpenTag() {
-    typeTextInFile(parseKeys("dit"), "abc<caret>defg</tag>hi");
-    myFixture.checkResult("abcdefg</tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInCloseWithoutOpenTag() {
-    typeTextInFile(parseKeys("dit"), "abcdefg</ta<caret>g>hi");
-    myFixture.checkResult("abcdefg</tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockAfterWithoutOpenTag() {
-    typeTextInFile(parseKeys("dit"), "abcdefg</tag>h<caret>i");
-    myFixture.checkResult("abcdefg</tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBeforeWithoutCloseTag() {
-    typeTextInFile(parseKeys("dit"), "abc<caret>defg<tag>hi");
-    myFixture.checkResult("abcdefg<tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInOpenWithoutCloseTag() {
-    typeTextInFile(parseKeys("dit"), "abcdefg<ta<caret>g>hi");
-    myFixture.checkResult("abcdefg<tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockAfterWithoutCloseTag() {
-    typeTextInFile(parseKeys("dit"), "abcdefg<tag>h<caret>i");
-    myFixture.checkResult("abcdefg<tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBeforeWrongOrder() {
-    typeTextInFile(parseKeys("dit"), "abc<caret>de</tag>fg<tag>hi");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInOpenWrongOrder() {
-    typeTextInFile(parseKeys("dit"), "abcde</ta<caret>g>fg<tag>hi");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBetweenWrongOrder() {
-    typeTextInFile(parseKeys("dit"), "abcde</tag>f<caret>g<tag>hi");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockInCloseWrongOrder() {
-    typeTextInFile(parseKeys("dit"), "abcde</tag>fg<ta<caret>g>hi");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockTwoTagsWrongOrder() {
-    typeTextInFile(parseKeys("dit"), "<foo><html>t<caret>ext</foo></html>");
-    myFixture.checkResult("<foo></foo></html>");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockTwoTagsWrongOrderInClosingTag() {
-    typeTextInFile(parseKeys("dit"), "<foo><html>text</foo></htm<caret>l>");
-    myFixture.checkResult("<foo><html></html>");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockAfterWrongOrder() {
-    typeTextInFile(parseKeys("dit"), "abcde</tag>fg<tag>h<caret>i");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBracketInside() {
-    typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret><>g</tag>hi");
-    myFixture.checkResult("abcde<tag></tag>hi");
-  }
-
-  //|d| |v_it|
-  public void testDeleteInnerTagBlockBracketInsideString() {
-    typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>\"<>\"g</tag>hi");
-    myFixture.checkResult("abcde<tag></tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockBefore() {
-    typeTextInFile(parseKeys("dat"), "abc<caret>de<tag>fg</tag>hi");
-    myFixture.checkResult("abcde<tag>fg</tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockInOpen() {
-    typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g>fg</tag>hi");
-    myFixture.checkResult("abcdehi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockInOpenWithArgs() {
-    typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g name = \"name\">fg</tag>hi");
-    myFixture.checkResult("abcdehi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockBetween() {
-    typeTextInFile(parseKeys("dat"), "abcde<tag>f<caret>g</tag>hi");
-    myFixture.checkResult("abcdehi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockBetweenWithArgs() {
-    typeTextInFile(parseKeys("dat"), "abcde<tag name = \"name\">f<caret>g</tag>hi");
-    myFixture.checkResult("abcdehi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockInClose() {
-    typeTextInFile(parseKeys("dat"), "abcde<tag>fg</ta<caret>g>hi");
-    myFixture.checkResult("abcdehi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockAfter() {
-    typeTextInFile(parseKeys("dat"), "abcde<tag>fg</tag>h<caret>i");
-    myFixture.checkResult("abcde<tag>fg</tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockInAlone() {
-    typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g>fghi");
-    myFixture.checkResult("abcde<tag>fghi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockWithoutTags() {
-    typeTextInFile(parseKeys("dat"), "abc<caret>de");
-    myFixture.checkResult("abcde");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockBeforeWithoutOpenTag() {
-    typeTextInFile(parseKeys("dat"), "abc<caret>defg</tag>hi");
-    myFixture.checkResult("abcdefg</tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockInCloseWithoutOpenTag() {
-    typeTextInFile(parseKeys("dat"), "abcdefg</ta<caret>g>hi");
-    myFixture.checkResult("abcdefg</tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockAfterWithoutOpenTag() {
-    typeTextInFile(parseKeys("dat"), "abcdefg</tag>h<caret>i");
-    myFixture.checkResult("abcdefg</tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockBeforeWithoutCloseTag() {
-    typeTextInFile(parseKeys("dat"), "abc<caret>defg<tag>hi");
-    myFixture.checkResult("abcdefg<tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockInOpenWithoutCloseTag() {
-    typeTextInFile(parseKeys("dat"), "abcdefg<ta<caret>g>hi");
-    myFixture.checkResult("abcdefg<tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockAfterWithoutCloseTag() {
-    typeTextInFile(parseKeys("dat"), "abcdefg<tag>h<caret>i");
-    myFixture.checkResult("abcdefg<tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockBeforeWrongOrder() {
-    typeTextInFile(parseKeys("dat"), "abc<caret>de</tag>fg<tag>hi");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockInOpenWrongOrder() {
-    typeTextInFile(parseKeys("dat"), "abcde</ta<caret>g>fg<tag>hi");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockBetweenWrongOrder() {
-    typeTextInFile(parseKeys("dat"), "abcde</tag>f<caret>g<tag>hi");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockInCloseWrongOrder() {
-    typeTextInFile(parseKeys("dat"), "abcde</tag>fg<ta<caret>g>hi");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  //|d| |v_at|
-  public void testDeleteOuterTagBlockAfterWrongOrder() {
-    typeTextInFile(parseKeys("dat"), "abcde</tag>fg<tag>h<caret>i");
-    myFixture.checkResult("abcde</tag>fg<tag>hi");
-  }
-
-  // |v_it|
-  public void testFileStartsWithSlash() {
-    configureByText("/*hello\n" +
-                    "<caret>foo\n" +
-                    "bar>baz\n");
-    typeText(parseKeys("vit"));
-    assertPluginError(true);
-  }
-
   // VIM-1427
   public void testDeleteOuterTagWithCount() {
     typeTextInFile(parseKeys("d2at"),"<a><b><c><caret></c></b></a>");
diff --git a/test/org/jetbrains/plugins/ideavim/action/RepeatActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/RepeatActionTest.kt
new file mode 100644
index 000000000..d07a8c30d
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/action/RepeatActionTest.kt
@@ -0,0 +1,73 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.jetbrains.plugins.ideavim.action
+
+import org.jetbrains.plugins.ideavim.VimTestCase
+import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
+import org.junit.Test
+
+class RepeatActionTest : VimTestCase() {
+
+    @Test
+    fun testSimpleRepeatLastCommand() {
+        configureByText("foo foo")
+        typeText(parseKeys("cw", "bar", "<Esc>", "w", "."))
+        myFixture.checkResult("bar bar")
+    }
+
+    @Test
+    fun testRepeatChangeToCharInNextLine() {
+        configureByText("The first line.\n" +
+                "This is the second line.\n" +
+                "Third line here, with a comma.\n" +
+                "Last line.")
+        typeText(parseKeys("j", "ct.", "Change the line to point", "<Esc>", "j0", "."))
+        myFixture.checkResult("The first line.\n" +
+                "Change the line to point.\n" +
+                "Change the line to point.\n" +
+                "Last line.")
+    }
+
+    // VIM-1644
+    @Test
+    fun testRepeatChangeInVisualMode() {
+        configureByText("foobar foobar")
+        typeText(parseKeys("<C-V>llc", "fu", "<Esc>", "w", "."))
+        myFixture.checkResult("fubar fubar")
+    }
+
+    // VIM-1644
+    @Test
+    fun testRepeatChangeInVisualModeMultiline() {
+        configureByText(
+            "There is a red house.\n" +
+            "Another red house there.\n" +
+            "They have red windows.\n" +
+            "Good."
+        )
+        typeText(parseKeys("www", "<C-V>ec", "blue", "<Esc>", "j0w.", "j0ww."))
+        myFixture.checkResult(
+            "There is a blue house.\n" +
+            "Another blue house there.\n" +
+            "They have blue windows.\n" +
+            "Good."
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/test/org/jetbrains/plugins/ideavim/action/change/insert/InsertDeleteInsertedTextActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/change/insert/InsertDeleteInsertedTextActionTest.kt
new file mode 100644
index 000000000..9a44baca7
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/action/change/insert/InsertDeleteInsertedTextActionTest.kt
@@ -0,0 +1,52 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.jetbrains.plugins.ideavim.action.change.insert
+
+import com.maddyhome.idea.vim.command.CommandState
+import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
+import org.jetbrains.plugins.ideavim.VimTestCase
+
+class InsertDeleteInsertedTextActionTest : VimTestCase() {
+    // VIM-1655
+    fun `test deleted text is not yanked`() {
+        doTest(parseKeys("yiw", "ea", "Hello", "<C-U>", "<ESC>p"), """
+            A Discovery
+
+            I found <caret>it in a legendary land
+        """.trimIndent(), """
+            A Discovery
+
+            I found iti<caret>t in a legendary land
+        """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
+    }
+
+    // VIM-1655
+    // VimBehaviourDiffers. Inserted text is not deleted after <C-U>
+    fun `test deleted text is not yanked after replace`() {
+        doTest(parseKeys("yiw", "eR", "Hello", "<C-U>", "<ESC>p"), """
+            A Discovery
+
+            I found <caret>it in a legendary land
+        """.trimIndent(), """
+            A Discovery
+
+            I found ii<caret>ta legendary land
+        """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
+    }
+}
\ No newline at end of file
diff --git a/src/com/maddyhome/idea/vim/action/ex/CancelExEntryAction.java b/test/org/jetbrains/plugins/ideavim/action/change/insert/InsertDeletePreviousWordActionTest.kt
similarity index 50%
rename from src/com/maddyhome/idea/vim/action/ex/CancelExEntryAction.java
rename to test/org/jetbrains/plugins/ideavim/action/change/insert/InsertDeletePreviousWordActionTest.kt
index 4257cdfa5..2b041e02b 100644
--- a/src/com/maddyhome/idea/vim/action/ex/CancelExEntryAction.java
+++ b/test/org/jetbrains/plugins/ideavim/action/change/insert/InsertDeletePreviousWordActionTest.kt
@@ -16,26 +16,23 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-package com.maddyhome.idea.vim.action.ex;
+package org.jetbrains.plugins.ideavim.action.change.insert
 
-import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.actionSystem.EditorAction;
-import com.maddyhome.idea.vim.VimPlugin;
-import com.maddyhome.idea.vim.command.Command;
-import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
-import org.jetbrains.annotations.NotNull;
+import com.maddyhome.idea.vim.command.CommandState
+import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
+import org.jetbrains.plugins.ideavim.VimTestCase
 
-/**
- */
-public class CancelExEntryAction extends EditorAction {
-  public CancelExEntryAction() {
-    super(new Handler());
-  }
+class InsertDeletePreviousWordActionTest : VimTestCase() {
+    // VIM-1655
+    fun `test deleted word is not yanked`() {
+        doTest(parseKeys("yiw", "3wea", "<C-W>", "<ESC>p"), """
+            A Discovery
 
-  private static class Handler extends EditorActionHandlerBase {
-    protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
-      return VimPlugin.getProcess().cancelExEntry(editor, context);
+            I found <caret>it in a legendary land
+        """.trimIndent(), """
+            A Discovery
+
+            I found it in a i<caret>t land
+        """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
     }
-  }
-}
+}
\ No newline at end of file
diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionRightActionTest.kt
index 656f0ebc0..eb0113592 100644
--- a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionRightActionTest.kt
+++ b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionRightActionTest.kt
@@ -22,7 +22,6 @@ package org.jetbrains.plugins.ideavim.action.motion.leftright
 
 import com.maddyhome.idea.vim.command.CommandState
 import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
-import com.maddyhome.idea.vim.helper.VimBehaviourDiffers
 import org.jetbrains.plugins.ideavim.VimTestCase
 
 class MotionRightActionTest : VimTestCase() {
@@ -80,14 +79,6 @@ class MotionRightActionTest : VimTestCase() {
         """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
     }
 
-    @VimBehaviourDiffers("""
-            A Discovery
-
-            I found it in a legendar𝛁<caret> land
-            all rocks and lavender and tufted grass,
-            where it was settled on some sodden sand
-            hard by the torrent of a mountain pass.
-    """)
     fun `test simple motion non-ascii`() {
         doTest(parseKeys("l"), """
             A Discovery
@@ -99,21 +90,13 @@ class MotionRightActionTest : VimTestCase() {
         """.trimIndent(), """
             A Discovery
 
-            I found it in a legendar<caret>𝛁 land
+            I found it in a legendar𝛁<caret> land
             all rocks and lavender and tufted grass,
             where it was settled on some sodden sand
             hard by the torrent of a mountain pass.
         """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
     }
 
-    @VimBehaviourDiffers("""
-            A Discovery
-
-            I found it in a legendar🐔<caret> land
-            all rocks and lavender and tufted grass,
-            where it was settled on some sodden sand
-            hard by the torrent of a mountain pass.
-    """)
     fun `test simple motion emoji`() {
         doTest(parseKeys("l"), """
             A Discovery
@@ -125,7 +108,25 @@ class MotionRightActionTest : VimTestCase() {
         """.trimIndent(), """
             A Discovery
 
-            I found it in a legendar<caret>🐔 land
+            I found it in a legendar🐔<caret> land
+            all rocks and lavender and tufted grass,
+            where it was settled on some sodden sand
+            hard by the torrent of a mountain pass.
+        """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
+    }
+
+    fun `test simple motion czech`() {
+        doTest(parseKeys("l"), """
+            A Discovery
+
+            I found it in a legendar<caret>ž land
+            all rocks and lavender and tufted grass,
+            where it was settled on some sodden sand
+            hard by the torrent of a mountain pass.
+        """.trimIndent(), """
+            A Discovery
+
+            I found it in a legendarž<caret> land
             all rocks and lavender and tufted grass,
             where it was settled on some sodden sand
             hard by the torrent of a mountain pass.
diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionInnerBlockParenActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionInnerBlockParenActionTest.kt
new file mode 100644
index 000000000..41fb35a6c
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionInnerBlockParenActionTest.kt
@@ -0,0 +1,129 @@
+package org.jetbrains.plugins.ideavim.action.motion.`object`
+
+import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
+import org.jetbrains.plugins.ideavim.VimTestCase
+
+class MotionInnerBlockParenActionTest : VimTestCase() {
+    // VIM-1633 |v_i)|
+    fun `test single letter with single parentheses`() {
+        configureByText("(<caret>a)")
+        typeText(parseKeys("vi)"))
+        assertSelection("a")
+    }
+
+    fun `test single letter with double parentheses`() {
+        configureByText("((<caret>a))")
+        typeText(parseKeys("vi)"))
+        assertSelection("(a)")
+    }
+
+    fun `test multiline outside parentheses`() {
+        configureByText("""(outer
+                        |<caret>(inner))""".trimMargin())
+        typeText(parseKeys("vi)"))
+        assertSelection("inner")
+    }
+
+    fun `test multiline in parentheses`() {
+        configureByText("""(outer
+                        |(inner<caret>))""".trimMargin())
+        typeText(parseKeys("vi)"))
+        assertSelection("inner")
+    }
+
+    fun `test multiline inside of outer parentheses`() {
+        configureByText("""(outer
+                         |<caret> (inner))""".trimMargin())
+        typeText(parseKeys("vi)"))
+        assertSelection("""outer
+                        | (inner)""".trimMargin())
+    }
+
+    fun `test double motion`() {
+        configureByText("""(outer
+                      |<caret>(inner))""".trimMargin())
+        typeText(parseKeys("vi)i)"))
+        assertSelection("""outer
+                          |(inner)""".trimMargin())
+    }
+
+    fun `test motion with count`() {
+        configureByText("""(outer
+                          |<caret>(inner))""".trimMargin())
+        typeText(parseKeys("v2i)"))
+        assertSelection("""outer
+                      |(inner)""".trimMargin())
+    }
+
+    fun `test text object after motion`() {
+        configureByText("""(outer
+                      |<caret>(inner))""".trimMargin())
+        typeText(parseKeys("vlli)"))
+        assertSelection("""outer
+                      |(inner)""".trimMargin())
+    }
+
+    fun `test text object after motion outside parentheses`() {
+        configureByText("""(outer
+                      |(inner<caret>))""".trimMargin())
+        typeText(parseKeys("vlli)"))
+        assertSelection("inner")
+    }
+
+    fun `test text object after motion inside parentheses`() {
+        configureByText("""(outer
+                      |(<caret>inner))""".trimMargin())
+        typeText(parseKeys("vllli)"))
+        assertSelection("inner")
+    }
+
+    // VIM-326 |d| |v_ib|
+    fun testDeleteInnerBlock() {
+        typeTextInFile(parseKeys("di)"),
+                "foo(\"b<caret>ar\")\n")
+        myFixture.checkResult("foo()\n")
+    }
+
+    // VIM-326 |d| |v_ib|
+    fun testDeleteInnerBlockCaretBeforeString() {
+        typeTextInFile(parseKeys("di)"),
+                "foo(<caret>\"bar\")\n")
+        myFixture.checkResult("foo()\n")
+    }
+
+    // VIM-326 |c| |v_ib|
+    fun testChangeInnerBlockCaretBeforeString() {
+        typeTextInFile(parseKeys("ci)"),
+                "foo(<caret>\"bar\")\n")
+        myFixture.checkResult("foo()\n")
+    }
+
+    // VIM-392 |c| |v_ib|
+    fun testChangeInnerBlockCaretBeforeBlock() {
+        typeTextInFile(parseKeys("ci)"),
+                "foo<caret>(bar)\n")
+        myFixture.checkResult("foo()\n")
+        assertOffset(4)
+    }
+
+    // |v_ib|
+    fun testInnerBlockCrashWhenNoDelimiterFound() {
+        typeTextInFile(parseKeys("di)"), "(x\n")
+        myFixture.checkResult("(x\n")
+    }
+
+    // VIM-275 |d| |v_ib|
+    fun testDeleteInnerParensBlockBeforeOpen() {
+        typeTextInFile(parseKeys("di)"),
+                "foo<caret>(bar)\n")
+        myFixture.checkResult("foo()\n")
+        assertOffset(4)
+    }
+
+    // |d| |v_ib|
+    fun testDeleteInnerParensBlockBeforeClose() {
+        typeTextInFile(parseKeys("di)"),
+                "foo(bar<caret>)\n")
+        myFixture.checkResult("foo()\n")
+    }
+}
\ No newline at end of file
diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionInnerBlockTagActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionInnerBlockTagActionTest.kt
new file mode 100644
index 000000000..b3bc9f0eb
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionInnerBlockTagActionTest.kt
@@ -0,0 +1,374 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.jetbrains.plugins.ideavim.action.motion.`object`
+
+import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
+import org.jetbrains.plugins.ideavim.VimTestCase
+
+class MotionInnerBlockTagActionTest : VimTestCase() {
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockCaretInHtml() {
+        typeTextInFile(parseKeys("dit"), "<template <caret>name=\"hello\">\n" +
+                "  <button>Click Me</button>\n" +
+                "  <p>You've pressed the button {{counter}} times.</p>\n" +
+                "</template>\n")
+        myFixture.checkResult("<template name=\"hello\"></template>\n")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockCaretInHtmlUnclosedTag() {
+        typeTextInFile(parseKeys("dit"), "<template <caret>name=\"hello\">\n" +
+                "  <button>Click Me</button>\n" +
+                "  <br>\n" +
+                "  <p>You've pressed the button {{counter}} times.</p>\n" +
+                "</template>\n")
+        myFixture.checkResult("<template name=\"hello\"></template>\n")
+    }
+
+    fun testDeleteInnerTagBlockCaretEdgeTag() {
+        typeTextInFile(parseKeys("dit"), "<template name=\"hello\"<caret>>\n" +
+                "  <button>Click Me</button>\n" +
+                "  <br>\n" +
+                "  <p>You've pressed the button {{counter}} times.</p>\n" +
+                "</template>\n")
+        myFixture.checkResult("<template name=\"hello\"></template>\n")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBefore() {
+        typeTextInFile(parseKeys("dit"), "abc<caret>de<tag>fg</tag>hi")
+        myFixture.checkResult("abcde<tag>fg</tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInOpen() {
+        typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fg</tag>hi")
+        myFixture.checkResult("abcde<tag></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInOpenEndOfLine() {
+        typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fg</tag>")
+        myFixture.checkResult("abcde<tag></tag>")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInOpenStartOfLine() {
+        typeTextInFile(parseKeys("dit"), "<ta<caret>g>fg</tag>hi")
+        myFixture.checkResult("<tag></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInOpenWithArgs() {
+        typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g name = \"name\">fg</tag>hi")
+        myFixture.checkResult("abcde<tag name = \"name\"></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBetween() {
+        typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>g</tag>hi")
+        myFixture.checkResult("abcde<tag></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBetweenTagWithRegex() {
+        typeTextInFile(parseKeys("dit"), "abcde<[abc]*>af<caret>gbc</[abc]*>hi")
+        myFixture.checkResult("abcde<[abc]*></[abc]*>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBetweenCamelCase() {
+        typeTextInFile(parseKeys("dit"), "abcde<tAg>f<caret>g</tag>hi")
+        myFixture.checkResult("abcde<tAg></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBetweenCaps() {
+        typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>g</TAG>hi")
+        myFixture.checkResult("abcde<tag></TAG>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBetweenWithSpaceBeforeTag() {
+        typeTextInFile(parseKeys("dit"), "abcde< tag>f<caret>g</ tag>hi")
+        myFixture.checkResult("abcde< tag>fg</ tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBetweenWithSpaceAfterTag() {
+        typeTextInFile(parseKeys("dit"), "abcde<tag >f<caret>g</tag>hi")
+        myFixture.checkResult("abcde<tag ></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBetweenWithArgs() {
+        typeTextInFile(parseKeys("dit"), "abcde<tag name = \"name\">f<caret>g</tag>hi")
+        myFixture.checkResult("abcde<tag name = \"name\"></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInClose() {
+        typeTextInFile(parseKeys("dit"), "abcde<tag>fg</ta<caret>g>hi")
+        myFixture.checkResult("abcde<tag></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockAfter() {
+        typeTextInFile(parseKeys("dit"), "abcde<tag>fg</tag>h<caret>i")
+        myFixture.checkResult("abcde<tag>fg</tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInAlone() {
+        typeTextInFile(parseKeys("dit"), "abcde<ta<caret>g>fghi")
+        myFixture.checkResult("abcde<tag>fghi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockWithoutTags() {
+        typeTextInFile(parseKeys("dit"), "abc<caret>de")
+        myFixture.checkResult("abcde")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBeforeWithoutOpenTag() {
+        typeTextInFile(parseKeys("dit"), "abc<caret>defg</tag>hi")
+        myFixture.checkResult("abcdefg</tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInCloseWithoutOpenTag() {
+        typeTextInFile(parseKeys("dit"), "abcdefg</ta<caret>g>hi")
+        myFixture.checkResult("abcdefg</tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockAfterWithoutOpenTag() {
+        typeTextInFile(parseKeys("dit"), "abcdefg</tag>h<caret>i")
+        myFixture.checkResult("abcdefg</tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBeforeWithoutCloseTag() {
+        typeTextInFile(parseKeys("dit"), "abc<caret>defg<tag>hi")
+        myFixture.checkResult("abcdefg<tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInOpenWithoutCloseTag() {
+        typeTextInFile(parseKeys("dit"), "abcdefg<ta<caret>g>hi")
+        myFixture.checkResult("abcdefg<tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockAfterWithoutCloseTag() {
+        typeTextInFile(parseKeys("dit"), "abcdefg<tag>h<caret>i")
+        myFixture.checkResult("abcdefg<tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBeforeWrongOrder() {
+        typeTextInFile(parseKeys("dit"), "abc<caret>de</tag>fg<tag>hi")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInOpenWrongOrder() {
+        typeTextInFile(parseKeys("dit"), "abcde</ta<caret>g>fg<tag>hi")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBetweenWrongOrder() {
+        typeTextInFile(parseKeys("dit"), "abcde</tag>f<caret>g<tag>hi")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockInCloseWrongOrder() {
+        typeTextInFile(parseKeys("dit"), "abcde</tag>fg<ta<caret>g>hi")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockTwoTagsWrongOrder() {
+        typeTextInFile(parseKeys("dit"), "<foo><html>t<caret>ext</foo></html>")
+        myFixture.checkResult("<foo></foo></html>")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockTwoTagsWrongOrderInClosingTag() {
+        typeTextInFile(parseKeys("dit"), "<foo><html>text</foo></htm<caret>l>")
+        myFixture.checkResult("<foo><html></html>")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockAfterWrongOrder() {
+        typeTextInFile(parseKeys("dit"), "abcde</tag>fg<tag>h<caret>i")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBracketInside() {
+        typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret><>g</tag>hi")
+        myFixture.checkResult("abcde<tag></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagBlockBracketInsideString() {
+        typeTextInFile(parseKeys("dit"), "abcde<tag>f<caret>\"<>\"g</tag>hi")
+        myFixture.checkResult("abcde<tag></tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagIsCaseInsensitive() {
+        typeTextInFile(parseKeys("dit"), "<a> <as<caret>df> </A>")
+        myFixture.checkResult("<a></A>")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagSlashesInAttribute() {
+        typeTextInFile(parseKeys("dit"), "<a href=\"http://isitchristmas.com\" class=\"button\">Bing <caret>Bing bing</a>")
+        myFixture.checkResult("<a href=\"http://isitchristmas.com\" class=\"button\"></a>")
+    }
+
+    // VIM-1090 |d| |v_it|
+    // Adapted from vim source file "test_textobjects.vim"
+    fun testDeleteInnerTagDuplicateTags() {
+        typeTextInFile(parseKeys("dit"), "<b>as<caret>d<i>as<b />df</i>asdf</b>")
+        myFixture.checkResult("<b></b>")
+    }
+
+    // |v_it|
+    fun testFileStartsWithSlash() {
+        configureByText("/*hello\n" +
+                "<caret>foo\n" +
+                "bar>baz\n")
+        typeText(parseKeys("vit"))
+        assertPluginError(true)
+    }
+
+    // |v_it|
+    fun testSelectInnerTagEmptyTag() {
+        configureByText("<a><caret></a>")
+        typeText(parseKeys("vit"))
+        assertSelection("<a></a>")
+    }
+
+    fun `test single character`() {
+        // The whole tag block is also selected if there is only a single character inside
+        configureByText("<a><caret>a</a>")
+        typeText(parseKeys("vit"))
+        assertSelection("<a>a</a>")
+    }
+
+    fun `test single character inside tag`() {
+        configureByText("<a<caret>></a>")
+        typeText(parseKeys("vit"))
+        assertSelection("<")
+    }
+
+    // VIM-1633 |v_it|
+    fun testNestedInTagSelection() {
+        configureByText("<t>Outer\n" +
+                "   <t><caret>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vit"))
+        assertSelection("Inner")
+    }
+
+    fun `test nested tag double motion`() {
+        configureByText("<o>Outer\n" +
+                " <caret>  <t></t>\n" +
+                "</o>\n")
+        typeText(parseKeys("vitit"))
+        assertSelection("<t></t>")
+    }
+
+    fun `test in inner tag double motion`() {
+        configureByText("<o><t><caret></t>\n</o>")
+        typeText(parseKeys("vitit"))
+        assertSelection("<o><t></t>\n</o>")
+    }
+
+    fun `test nested tags between tags`() {
+        configureByText("<t>Outer\n" +
+                "   <t>Inner</t> <caret> <t>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vit"))
+        assertSelection("Outer\n" + "   <t>Inner</t>  <t>Inner</t>")
+    }
+
+    fun `test nested tags number motion`() {
+        configureByText("<t>Outer\n" +
+                "   <t><caret>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("v2it"))
+        assertSelection("Outer\n" + "   <t>Inner</t>")
+    }
+
+    fun `test nested tags double motion`() {
+        configureByText("<o>Outer\n" +
+                "   <t><caret>Inner</t>\n" +
+                "</o>\n")
+        typeText(parseKeys("vitit"))
+        assertSelection("<t>Inner</t>")
+    }
+
+    fun `test nested tags triple motion`() {
+        configureByText("<t>Outer\n" +
+                "   <t><caret>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vititit"))
+        assertSelection("Outer\n" + "   <t>Inner</t>")
+    }
+
+    fun `test nested tags in closing tag`() {
+        configureByText("<t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</<caret>t>\n")
+        typeText(parseKeys("vit"))
+        assertSelection("Outer\n" + "   <t>Inner</t>")
+    }
+
+    fun `test nested tags in opening tag`() {
+        configureByText("<<caret>t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vit"))
+        assertSelection("Outer\n" + "   <t>Inner</t>")
+    }
+
+    fun `test nested tags ouside tag`() {
+        configureByText("<caret><t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vit"))
+        assertSelection("Outer\n" + "   <t>Inner</t>")
+    }
+
+    fun `test skip whitespace at start of line`() {
+        configureByText("<o>Outer\n" +
+                " <caret>  <t></t>\n" +
+                "</o>\n")
+        typeText(parseKeys("vit"))
+        assertSelection("<")
+    }
+}
\ No newline at end of file
diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionOuterBlockParenActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionOuterBlockParenActionTest.kt
new file mode 100644
index 000000000..0393cfabb
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionOuterBlockParenActionTest.kt
@@ -0,0 +1,79 @@
+package org.jetbrains.plugins.ideavim.action.motion.`object`
+
+import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
+import org.jetbrains.plugins.ideavim.VimTestCase
+
+class MotionOuterBlockParenActionTest : VimTestCase() {
+    // VIM-1633 |v_a)|
+    fun `test single letter with single parentheses`() {
+        configureByText("(<caret>a)")
+        typeText(parseKeys("va)"))
+        assertSelection("(a)")
+    }
+
+    fun `test single letter with double parentheses`() {
+        configureByText("((<caret>a))")
+        typeText(parseKeys("va)"))
+        assertSelection("(a)")
+    }
+
+    fun `test multiline outside parentheses`() {
+        configureByText("""(outer
+                      |<caret>(inner))""".trimMargin())
+        typeText(parseKeys("va)"))
+        assertSelection("(inner)")
+    }
+
+    fun `test multiline in parentheses`() {
+        configureByText("""(outer
+                      |(inner<caret>))""".trimMargin())
+        typeText(parseKeys("va)"))
+        assertSelection("(inner)")
+    }
+
+    fun `test multiline inside of outer parentheses`() {
+        configureByText("""(outer
+                     |<caret> (inner))""".trimMargin())
+        typeText(parseKeys("va)"))
+        assertSelection("""(outer
+                        | (inner))""".trimMargin())
+    }
+
+    fun `test double motion`() {
+        configureByText("""(outer
+                      |<caret>(inner))""".trimMargin())
+        typeText(parseKeys("va)a)"))
+        assertSelection("""(outer
+                      |(inner))""".trimMargin())
+    }
+
+    fun `test motion with count`() {
+        configureByText("""(outer
+                      |<caret>(inner))""".trimMargin())
+        typeText(parseKeys("v2a)"))
+        assertSelection("""(outer
+                      |(inner))""".trimMargin())
+    }
+
+    fun `test text object after motion`() {
+        configureByText("""(outer
+                      |<caret>(inner))""".trimMargin())
+        typeText(parseKeys("vlla)"))
+        assertSelection("""(outer
+                      |(inner))""".trimMargin())
+    }
+
+    fun `test text object after motion outside parentheses`() {
+        configureByText("""(outer
+                      |(inner<caret>))""".trimMargin())
+        typeText(parseKeys("vlla)"))
+        assertSelection("(inner)")
+    }
+
+    // |d| |v_ab|
+    fun testDeleteOuterBlock() {
+        typeTextInFile(parseKeys("da)"),
+                "foo(b<caret>ar, baz);\n")
+        myFixture.checkResult("foo;\n")
+    }
+}
\ No newline at end of file
diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionOuterBlockTagActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionOuterBlockTagActionTest.kt
new file mode 100644
index 000000000..8701beb06
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/action/motion/object/MotionOuterBlockTagActionTest.kt
@@ -0,0 +1,254 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.jetbrains.plugins.ideavim.action.motion.`object`
+
+import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
+import org.jetbrains.plugins.ideavim.VimTestCase
+
+class MotionOuterBlockTagActionTest : VimTestCase() {
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockBefore() {
+        typeTextInFile(parseKeys("dat"), "abc<caret>de<tag>fg</tag>hi")
+        myFixture.checkResult("abcde<tag>fg</tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockInOpen() {
+        typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g>fg</tag>hi")
+        myFixture.checkResult("abcdehi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockInOpenWithArgs() {
+        typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g name = \"name\">fg</tag>hi")
+        myFixture.checkResult("abcdehi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockBetween() {
+        typeTextInFile(parseKeys("dat"), "abcde<tag>f<caret>g</tag>hi")
+        myFixture.checkResult("abcdehi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockBetweenWithArgs() {
+        typeTextInFile(parseKeys("dat"), "abcde<tag name = \"name\">f<caret>g</tag>hi")
+        myFixture.checkResult("abcdehi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockInClose() {
+        typeTextInFile(parseKeys("dat"), "abcde<tag>fg</ta<caret>g>hi")
+        myFixture.checkResult("abcdehi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockAfter() {
+        typeTextInFile(parseKeys("dat"), "abcde<tag>fg</tag>h<caret>i")
+        myFixture.checkResult("abcde<tag>fg</tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockInAlone() {
+        typeTextInFile(parseKeys("dat"), "abcde<ta<caret>g>fghi")
+        myFixture.checkResult("abcde<tag>fghi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockWithoutTags() {
+        typeTextInFile(parseKeys("dat"), "abc<caret>de")
+        myFixture.checkResult("abcde")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockBeforeWithoutOpenTag() {
+        typeTextInFile(parseKeys("dat"), "abc<caret>defg</tag>hi")
+        myFixture.checkResult("abcdefg</tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockInCloseWithoutOpenTag() {
+        typeTextInFile(parseKeys("dat"), "abcdefg</ta<caret>g>hi")
+        myFixture.checkResult("abcdefg</tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockAfterWithoutOpenTag() {
+        typeTextInFile(parseKeys("dat"), "abcdefg</tag>h<caret>i")
+        myFixture.checkResult("abcdefg</tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockBeforeWithoutCloseTag() {
+        typeTextInFile(parseKeys("dat"), "abc<caret>defg<tag>hi")
+        myFixture.checkResult("abcdefg<tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockInOpenWithoutCloseTag() {
+        typeTextInFile(parseKeys("dat"), "abcdefg<ta<caret>g>hi")
+        myFixture.checkResult("abcdefg<tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockAfterWithoutCloseTag() {
+        typeTextInFile(parseKeys("dat"), "abcdefg<tag>h<caret>i")
+        myFixture.checkResult("abcdefg<tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockBeforeWrongOrder() {
+        typeTextInFile(parseKeys("dat"), "abc<caret>de</tag>fg<tag>hi")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockInOpenWrongOrder() {
+        typeTextInFile(parseKeys("dat"), "abcde</ta<caret>g>fg<tag>hi")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockBetweenWrongOrder() {
+        typeTextInFile(parseKeys("dat"), "abcde</tag>f<caret>g<tag>hi")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockInCloseWrongOrder() {
+        typeTextInFile(parseKeys("dat"), "abcde</tag>fg<ta<caret>g>hi")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_at|
+    fun testDeleteOuterTagBlockAfterWrongOrder() {
+        typeTextInFile(parseKeys("dat"), "abcde</tag>fg<tag>h<caret>i")
+        myFixture.checkResult("abcde</tag>fg<tag>hi")
+    }
+
+    //|d| |v_it|
+    fun testDeleteInnerTagAngleBrackets() {
+        typeTextInFile(parseKeys("dit"), "<div <caret>hello=\"d > hsj < akl\"></div>")
+        myFixture.checkResult("<div hello=\"d ></div>")
+    }
+
+    // VIM-1090 |d| |v_at|
+    fun testDeleteOuterTagDuplicateTags() {
+        typeTextInFile(parseKeys("dat"), "<a><a></a></a<caret>>")
+        myFixture.checkResult("")
+    }
+
+    // |v_it| |v_at|
+    fun testTagSelectionSkipsWhitespaceAtStartOfLine() {
+        // Also skip tabs
+        configureByText("<o>Outer\n" +
+                " <caret> \t <t>Inner</t>\n" +
+                "</o>\n")
+        typeText(parseKeys("vat"))
+        assertSelection("<t>Inner</t>")
+    }
+
+    fun `test skip new line`() {
+        // Newline must not be skipped
+        configureByText("<caret>\n" + "    <t>asdf</t>")
+        typeText(parseKeys("vat"))
+        assertSelection(null)
+    }
+
+    fun `test whitespace skip`() {
+        // Whitespace is only skipped if there is nothing else at the start of the line
+        configureByText("<o>Outer\n" +
+                "a <caret>  <t>Inner</t>\n" +
+                "</o>\n")
+        typeText(parseKeys("vat"))
+        assertSelection("<o>Outer\n" +
+                "a   <t>Inner</t>\n" +
+                "</o>")
+    }
+
+    // |v_at|
+    fun testNestedTagSelection() {
+        configureByText("<t>Outer\n" +
+                "   <t><caret>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vat"))
+        assertSelection("<t>Inner</t>")
+    }
+
+    fun `test nested tags between tags`() {
+        configureByText("<t>Outer\n" +
+                "   <t>Inner</t> <caret> <t>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vat"))
+        assertSelection("<t>Outer\n" +
+                "   <t>Inner</t>  <t>Inner</t>\n" +
+                "</t>")
+    }
+
+    fun `test nested tags double motion`() {
+        configureByText("<t>Outer\n" +
+                "   <t><caret>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vatat"))
+        assertSelection("<t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</t>")
+    }
+
+    fun `test nested tags number motion`() {
+        configureByText("<t>Outer\n" +
+                "   <t><caret>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("v2at"))
+        assertSelection("<t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</t>")
+    }
+
+    fun `test nested tags on outer`() {
+        configureByText("<t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</<caret>t>\n")
+        typeText(parseKeys("vat"))
+        assertSelection("<t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</t>")
+    }
+
+    fun `test nested tags on outer start`() {
+        configureByText("<<caret>t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vat"))
+        assertSelection("<t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</t>")
+    }
+
+    fun `test nested tags outside outer`() {
+        configureByText("<caret><t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</t>\n")
+        typeText(parseKeys("vat"))
+        assertSelection("<t>Outer\n" +
+                "   <t>Inner</t>\n" +
+                "</t>")
+    }
+}
\ No newline at end of file
diff --git a/test/org/jetbrains/plugins/ideavim/ex/ActionListCommandTest.java b/test/org/jetbrains/plugins/ideavim/ex/ActionListCommandTest.java
new file mode 100644
index 000000000..54122733e
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/ex/ActionListCommandTest.java
@@ -0,0 +1,80 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.jetbrains.plugins.ideavim.ex;
+
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.maddyhome.idea.vim.ex.ExOutputModel;
+import org.jetbrains.plugins.ideavim.VimTestCase;
+
+/**
+ * @author Naoto Ikeno
+ */
+public class ActionListCommandTest extends VimTestCase {
+  public void testListAllActions() {
+    configureByText("\n");
+    typeText(commandToKeys("actionlist"));
+
+    String output = ExOutputModel.getInstance(myFixture.getEditor()).getText();
+    assertNotNull(output);
+
+    // Header line
+    String[] displayedLines = output.split("\n");
+    assertEquals("--- Actions ---", displayedLines[0]);
+
+    // Action lines
+    int displayedActionNum = displayedLines.length - 1;
+    String[] actionIds = ActionManager.getInstance().getActionIds("");
+    assertEquals(displayedActionNum, actionIds.length);
+  }
+
+  public void testSearchByActionName() {
+    configureByText("\n");
+    typeText(commandToKeys("actionlist quickimpl"));
+
+    String[] displayedLines = parseActionListOutput();
+    for (int i = 0; i < displayedLines.length; i++) {
+      String line = displayedLines[i];
+      if (i == 0) {
+        assertEquals("--- Actions ---", line);
+      }else {
+        assertTrue(line.toLowerCase().contains("quickimpl"));
+      }
+    }
+  }
+
+  public void testSearchByAssignedShortcutKey() {
+    configureByText("\n");
+    typeText(commandToKeys("actionlist <M-S-"));
+
+    String[] displayedLines = parseActionListOutput();
+    for (int i = 0; i < displayedLines.length; i++) {
+      String line = displayedLines[i];
+      if (i == 0) {
+        assertEquals("--- Actions ---", line);
+      }else {
+        assertTrue(line.toLowerCase().contains("<m-s-"));
+      }
+    }
+  }
+
+  private String[] parseActionListOutput() {
+    String output = ExOutputModel.getInstance(myFixture.getEditor()).getText();
+    return output == null ? new String[]{} : output.split("\n");
+  }
+}
diff --git a/test/org/jetbrains/plugins/ideavim/ex/ExEntryTest.kt b/test/org/jetbrains/plugins/ideavim/ex/ExEntryTest.kt
new file mode 100644
index 000000000..83f5c1988
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/ex/ExEntryTest.kt
@@ -0,0 +1,578 @@
+package org.jetbrains.plugins.ideavim.ex
+
+import com.maddyhome.idea.vim.VimPlugin
+import com.maddyhome.idea.vim.helper.StringHelper
+import com.maddyhome.idea.vim.option.Options
+import com.maddyhome.idea.vim.ui.ExDocument
+import com.maddyhome.idea.vim.ui.ExEntryPanel
+import org.jetbrains.plugins.ideavim.VimTestCase
+import java.awt.event.KeyEvent
+import javax.swing.KeyStroke
+
+class ExEntryTest: VimTestCase() {
+    override fun setUp() {
+        super.setUp()
+        configureByText("\n")
+    }
+
+    fun `test cancel entry`() {
+        val options = Options.getInstance()
+
+        assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
+        typeExInput(":set incsearch<Esc>")
+        assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
+        assertIsDeactivated()
+
+        deactivateExEntry()
+
+        assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
+        typeExInput(":set incsearch<C-[>")
+        assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
+        assertIsDeactivated()
+
+        deactivateExEntry()
+
+        assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
+        typeExInput(":set incsearch<C-C>")
+        assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
+        assertIsDeactivated()
+    }
+
+    fun `test complete entry`() {
+        val options = Options.getInstance()
+
+        assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
+        typeExInput(":set incsearch<Enter>")
+        assertTrue(options.isSet(Options.INCREMENTAL_SEARCH))
+        assertIsDeactivated()
+
+        deactivateExEntry()
+        options.resetAllOptions()
+
+        assertFalse(options.isSet(Options.INCREMENTAL_SEARCH))
+        typeExInput(":set incsearch<C-J>")
+        assertTrue(options.isSet(Options.INCREMENTAL_SEARCH))
+        assertIsDeactivated()
+
+        deactivateExEntry()
+        options.resetAllOptions()
+
+        assertFalse(Options.getInstance().isSet(Options.INCREMENTAL_SEARCH))
+        typeExInput(":set incsearch<C-M>")
+        assertTrue(Options.getInstance().isSet(Options.INCREMENTAL_SEARCH))
+        assertIsDeactivated()
+    }
+
+    fun `test caret shape`() {
+        // Show block at end of input (normal)
+        // Show vertical bar in insert mode
+        // Show horizontal bar in replace mode
+        typeExInput(":")
+        assertEquals("BLOCK 100", exEntryPanel.entry.caretShape)
+
+        typeText("set")
+        assertEquals("BLOCK 100", exEntryPanel.entry.caretShape)
+
+        deactivateExEntry()
+        typeExInput(":set<Home>")
+        assertEquals("VER 25", exEntryPanel.entry.caretShape)
+
+        deactivateExEntry()
+        typeExInput(":set<Home><Insert>")
+        assertEquals("HOR 20", exEntryPanel.entry.caretShape)
+
+        deactivateExEntry()
+        typeExInput(":set<Home><Insert><Insert>")
+        assertEquals("VER 25", exEntryPanel.entry.caretShape)
+    }
+
+    fun `test move caret to beginning of line`() {
+        typeExInput(":set incsearch<C-B>")
+        assertExOffset(0)
+
+        deactivateExEntry()
+
+        typeExInput(":set incsearch<Home>")
+        assertExOffset(0)
+    }
+
+    fun `test move caret to end of line`() {
+        typeExInput(":set incsearch<C-B>")
+        assertExOffset(0)
+
+        typeText("<C-E>")
+        assertExOffset(13)
+
+        deactivateExEntry()
+        typeExInput(":set incsearch<C-B>")
+        assertExOffset(0)
+
+        typeText("<End>")
+        assertExOffset(13)
+    }
+
+    fun `test delete character in front of caret`() {
+        typeExInput(":set incsearch<BS>")
+        assertExText("set incsearc")
+
+        typeText("<C-H>")
+        assertExText("set incsear")
+    }
+
+    fun `test delete character in front of caret cancels entry`() {
+        typeExInput(":<BS>")
+        assertIsDeactivated()
+
+        deactivateExEntry()
+
+        typeExInput(":set<BS><BS><BS><BS>")
+        assertIsDeactivated()
+
+        deactivateExEntry()
+
+        typeExInput(":<C-H>")
+        assertIsDeactivated()
+
+        deactivateExEntry()
+
+        // TODO: Vim behaviour is to NOT deactivate if there is still text
+        typeExInput(":set<C-B>")
+        assertExOffset(0)
+        typeText("<BS>")
+        assertIsDeactivated()
+    }
+
+    fun `test delete character under caret`() {
+        typeExInput(":set<Left>")
+        typeText("<Del>")
+        assertExText("se")
+    }
+
+    fun `test delete word before caret`() {
+        typeExInput(":set incsearch<C-W>")
+        assertExText("set ")
+
+        deactivateExEntry()
+
+        typeExInput(":set incsearch<Left><Left><Left>")
+        typeText("<C-W>")
+        assertExText("set rch")
+    }
+
+    fun `test delete to start of line`() {
+        typeExInput(":set incsearch<C-U>")
+        assertExText("")
+
+        deactivateExEntry()
+
+        typeExInput(":set incsearch<Left><Left><Left><C-U>")
+        assertExText("rch")
+    }
+
+    fun `test command history`() {
+        typeExInput(":set digraph<CR>")
+        typeExInput(":digraph<CR>")
+        typeExInput(":set incsearch<CR>")
+
+        typeExInput(":<Up>")
+        assertExText("set incsearch")
+        typeText("<Up>")
+        assertExText("digraph")
+        typeText("<Up>")
+        assertExText("set digraph")
+
+        deactivateExEntry()
+
+        // TODO: Vim behaviour reorders the history even when cancelling history
+//        typeExInput(":<Up>")
+//        assertExText("set digraph")
+//        typeText("<Up>")
+//        assertExText("set incsearch")
+
+        typeExInput(":<S-Up>")
+        assertExText("set incsearch")
+        typeText("<Up>")
+        assertExText("digraph")
+        typeText("<Up>")
+        assertExText("set digraph")
+
+        deactivateExEntry()
+
+        typeExInput(":<PageUp>")
+        assertExText("set incsearch")
+        typeText("<PageUp>")
+        assertExText("digraph")
+        typeText("<PageUp>")
+        assertExText("set digraph")
+    }
+
+    fun `test matching command history`() {
+        typeExInput(":set digraph<CR>")
+        typeExInput(":digraph<CR>")
+        typeExInput(":set incsearch<CR>")
+
+        typeExInput(":set<Up>")
+        assertExText("set incsearch")
+        typeText("<Up>")
+        assertExText("set digraph")
+
+        deactivateExEntry()
+
+        typeExInput(":set<S-Up>")
+        assertExText("set incsearch")
+        typeText("<S-Up>")
+        assertExText("digraph")
+        typeText("<S-Up>")
+        assertExText("set digraph")
+
+        deactivateExEntry()
+
+        typeExInput(":set<PageUp>")
+        assertExText("set incsearch")
+        typeText("<PageUp>")
+        assertExText("digraph")
+        typeText("<PageUp>")
+        assertExText("set digraph")
+    }
+
+    fun `test search history`() {
+        typeExInput("/something cool<CR>")
+        typeExInput("/not cool<CR>")
+        typeExInput("/so cool<CR>")
+
+        typeExInput("/<Up>")
+        assertExText("so cool")
+        typeText("<Up>")
+        assertExText("not cool")
+        typeText("<Up>")
+        assertExText("something cool")
+
+        deactivateExEntry()
+
+        typeExInput("/<S-Up>")
+        assertExText("so cool")
+        typeText("<S-Up>")
+        assertExText("not cool")
+        typeText("<S-Up>")
+        assertExText("something cool")
+
+        deactivateExEntry()
+
+        typeExInput("/<PageUp>")
+        assertExText("so cool")
+        typeText("<PageUp>")
+        assertExText("not cool")
+        typeText("<PageUp>")
+        assertExText("something cool")
+    }
+
+    fun `test matching search history`() {
+        typeExInput("/something cool<CR>")
+        typeExInput("/not cool<CR>")
+        typeExInput("/so cool<CR>")
+
+        typeExInput("/so<Up>")
+        assertExText("so cool")
+        typeText("<Up>")
+        assertExText("something cool")
+
+        deactivateExEntry()
+
+        // TODO: Vim behaviour reorders the history even when cancelling history
+//        typeExInput(":<Up>")
+//        assertEquals("set digraph", exEntryPanel.text)
+//        typeText("<Up>")
+//        assertEquals("set incsearch", exEntryPanel.text)
+
+        typeExInput("/so<S-Up>")
+        assertExText("so cool")
+        typeText("<S-Up>")
+        assertExText("not cool")
+        typeText("<S-Up>")
+        assertExText("something cool")
+
+        deactivateExEntry()
+
+        typeExInput("/so<PageUp>")
+        assertExText("so cool")
+        typeText("<PageUp>")
+        assertExText("not cool")
+        typeText("<PageUp>")
+        assertExText("something cool")
+    }
+
+    fun `test toggle insert replace`() {
+        val exDocument = exEntryPanel.entry.document as ExDocument
+        assertFalse(exDocument.isOverwrite)
+        typeExInput(":set<C-B>digraph")
+        assertExText("digraphset")
+
+        deactivateExEntry()
+
+        typeExInput(":set<C-B><Insert>digraph")
+        assertTrue(exDocument.isOverwrite)
+        assertExText("digraph")
+
+        typeText("<Insert><C-B>set ")
+        assertFalse(exDocument.isOverwrite)
+        assertExText("set digraph")
+    }
+
+    fun `test move caret one WORD left`() {
+        typeExInput(":set incsearch<S-Left>")
+        assertExOffset(4)
+        typeText("<S-Left>")
+        assertExOffset(0)
+
+        deactivateExEntry()
+
+        typeExInput(":set incsearch<C-Left>")
+        assertExOffset(4)
+        typeText("<C-Left>")
+        assertExOffset(0)
+    }
+
+    fun `test move caret one WORD right`() {
+        typeExInput(":set incsearch")
+        caret.dot = 0
+        typeText("<S-Right>")
+        // TODO: Vim moves caret to "set| ", while we move it to "set |"
+        assertExOffset(4)
+
+        typeText("<S-Right>")
+        assertExOffset(13)
+
+        caret.dot = 0
+        typeText("<C-Right>")
+        // TODO: Vim moves caret to "set| ", while we move it to "set |"
+        assertExOffset(4)
+
+        typeText("<C-Right>")
+        assertExOffset(13)
+    }
+
+    fun `test insert digraph`() {
+        typeExInput(":<C-K>OK")
+        assertExText("✓")
+        assertExOffset(1)
+
+        deactivateExEntry()
+
+        typeExInput(":set<Home><C-K>OK")
+        assertExText("✓set")
+        assertExOffset(1)
+
+        deactivateExEntry()
+
+        typeExInput(":set<Home><Insert><C-K>OK")
+        assertExText("✓et")
+        assertExOffset(1)
+    }
+
+    fun `test prompt while inserting digraph`() {
+        typeExInput(":<C-K>")
+        assertExText("?")
+        assertExOffset(0)
+
+        deactivateExEntry()
+
+        typeExInput(":<C-K>O")
+        assertExText("O")
+        assertExOffset(0)
+
+        deactivateExEntry()
+
+        typeExInput(":set<Home><C-K>")
+        assertExText("?set")
+        assertExOffset(0)
+
+        deactivateExEntry()
+
+        typeExInput(":set<Home><C-K>O")
+        assertExText("Oset")
+        assertExOffset(0)
+    }
+
+    fun `test escape cancels digraph`() {
+        typeExInput(":<C-K><Esc>OK")
+        assertIsActive()
+        assertExText("OK")
+
+        deactivateExEntry()
+
+        // Note that the docs state that hitting escape stops digraph entry and cancels command line mode. In practice,
+        // this isn't true - digraph entry is stopped, but command line mode continues
+        typeExInput(":<C-K>O<Esc>K")
+        assertIsActive()
+        assertEquals("K", exEntryPanel.text)
+
+        deactivateExEntry()
+    }
+
+    // TODO: Test inserting control characters, if/when supported
+
+    fun `test insert literal character`() {
+        typeExInput(":<C-V>123<C-V>080")
+        assertExText("{P")
+
+        deactivateExEntry()
+
+        typeExInput(":<C-V>o123")
+        assertExText("S")
+
+        deactivateExEntry()
+
+        typeExInput(":<C-V>u00A9")
+        assertExText("©")
+
+        deactivateExEntry()
+
+        typeExInput(":<C-Q>123<C-Q>080")
+        assertExText("{P")
+
+        deactivateExEntry()
+
+        typeExInput(":<C-Q>o123")
+        assertExText("S")
+
+        deactivateExEntry()
+
+        typeExInput(":<C-Q>u00a9")
+        assertExText("©")
+
+        deactivateExEntry()
+
+        typeExInput(":set<Home><C-V>u00A9")
+        assertExText("©set")
+        assertExOffset(1)
+    }
+
+    fun `test prompt while inserting literal character`() {
+        typeExInput(":<C-V>")
+        assertExText("^")
+        assertExOffset(0)
+
+        deactivateExEntry()
+
+        typeExInput(":<C-V>o")
+        assertExText("^")
+        assertExOffset(0)
+
+        typeText("1")
+        assertExText("^")
+        assertExOffset(0)
+
+        typeText("2")
+        assertExText("^")
+        assertExOffset(0)
+
+        typeText("3")
+        assertExText("S")
+        assertExOffset(1)
+    }
+
+    fun `test insert register`() {
+        VimPlugin.getRegister().setKeys('c', StringHelper.parseKeys("hello world"))
+        VimPlugin.getRegister().setKeys('5', StringHelper.parseKeys("greetings programs"))
+
+        typeExInput(":<C-R>c")
+        assertExText("hello world")
+
+        deactivateExEntry()
+
+        typeExInput(":<C-R>5")
+        assertExText("greetings programs")
+
+        deactivateExEntry()
+
+        typeExInput(":set<Home><C-R>c")
+        assertExText("hello worldset")
+        assertExOffset(11)  // Just before 'set'
+
+        // TODO: Test caret feedback
+        // Vim shows " after hitting <C-R>
+    }
+
+    fun `test insert multi-line register`() {
+        // parseKeys parses <CR> in a way that Register#getText doesn't like
+        val keys = mutableListOf<KeyStroke>()
+        keys.addAll(StringHelper.parseKeys("hello"))
+        keys.add(KeyStroke.getKeyStroke('\n'))
+        keys.addAll(StringHelper.parseKeys("world"))
+        VimPlugin.getRegister().setKeys('c', keys)
+
+        typeExInput(":<C-R>c")
+        assertExText("hello world")
+    }
+
+    // TODO: Test other special registers, if/when supported
+    // E.g. '.' '%' '#', etc.
+
+    fun `test insert last command`() {
+        typeExInput(":set incsearch<CR>")
+        typeExInput(":<C-R>:")
+        assertExText("set incsearch")
+    }
+
+    fun `test insert last search command`() {
+        typeExInput("/hello<CR>")
+        typeExInput(":<C-R>/")
+        assertExText("hello")
+    }
+
+    private fun typeExInput(text: String) {
+        assertTrue("Ex command must start with ':', '/' or '?'",
+            text.startsWith(":") || text.startsWith('/') || text.startsWith('?'))
+
+        val keys = mutableListOf<KeyStroke>()
+        StringHelper.parseKeys(text).forEach {
+            // <Left> doesn't work correctly in tests. The DefaultEditorKit.NextVisualPositionAction action is correctly
+            // called, but fails to move the caret correctly because the text component has never been painted
+            if (it.keyCode == KeyEvent.VK_LEFT && it.modifiers == 0) {
+                if (keys.count() > 0) {
+                    typeText(keys)
+                    keys.clear()
+                }
+
+                exEntryPanel.entry.caret.dot--
+            }
+            else {
+                keys.add(it)
+            }
+        }
+        if (keys.count() > 0)
+            typeText(keys)
+    }
+
+    private fun typeText(text: String) {
+        typeText(StringHelper.parseKeys(text))
+    }
+
+    private fun deactivateExEntry() {
+        // We don't need to reset text, that's handled by #active
+        if (exEntryPanel.isActive)
+            typeText("<C-C>")
+    }
+
+    private fun assertExText(expected: String) {
+        assertEquals(expected, exEntryPanel.text)
+    }
+
+    private fun assertIsActive() {
+        assertTrue(exEntryPanel.isActive)
+    }
+
+    private fun assertIsDeactivated() {
+        assertFalse(exEntryPanel.isActive)
+    }
+
+    private fun assertExOffset(expected: Int) {
+        assertEquals(expected, caret.dot)
+    }
+
+    private val exEntryPanel
+        get() = ExEntryPanel.getInstance()
+
+    private val caret
+        get() = exEntryPanel.entry.caret
+}
\ No newline at end of file
diff --git a/test/org/jetbrains/plugins/ideavim/ex/handler/CmdClearHandlerTest.kt b/test/org/jetbrains/plugins/ideavim/ex/handler/CmdClearHandlerTest.kt
new file mode 100644
index 000000000..590ea1d15
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/ex/handler/CmdClearHandlerTest.kt
@@ -0,0 +1,56 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.jetbrains.plugins.ideavim.ex.handler
+import com.maddyhome.idea.vim.VimPlugin
+import org.jetbrains.plugins.ideavim.VimFileEditorTestCase
+
+/**
+ * @author Elliot Courant
+ */
+class CmdClearHandlerTest : VimFileEditorTestCase() {
+    fun `test clear aliases`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(commandToKeys("command"))
+        assertPluginError(false)
+        assertExOutput("Name        Args       Definition\n") // There should not be any aliases.
+
+        typeText(commandToKeys("command Vs vs"))
+        assertPluginError(false)
+        typeText(commandToKeys("command Wq wq"))
+        assertPluginError(false)
+        typeText(commandToKeys("command WQ wq"))
+        assertPluginError(false)
+        typeText(commandToKeys("command"))
+        assertPluginError(false)
+        // The added alias should be listed
+        assertExOutput("""Name        Args       Definition
+            |Vs          0          vs
+            |Wq          0          wq
+            |WQ          0          wq
+        """.trimMargin())
+
+        // Delete all of the aliases and then list aliases again.
+        typeText(commandToKeys("comclear"))
+        assertPluginError(false)
+        typeText(commandToKeys("command"))
+        assertPluginError(false)
+        assertExOutput("Name        Args       Definition\n") // There should not be any aliases.
+    }
+}
\ No newline at end of file
diff --git a/test/org/jetbrains/plugins/ideavim/ex/handler/CmdHandlerTest.kt b/test/org/jetbrains/plugins/ideavim/ex/handler/CmdHandlerTest.kt
new file mode 100644
index 000000000..ebde4240f
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/ex/handler/CmdHandlerTest.kt
@@ -0,0 +1,207 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.jetbrains.plugins.ideavim.ex.handler
+
+import com.maddyhome.idea.vim.VimPlugin
+import org.jetbrains.plugins.ideavim.VimFileEditorTestCase
+import org.jetbrains.plugins.ideavim.VimTestCase
+
+/**
+ * @author Elliot Courant
+ */
+class CmdHandlerTest : VimFileEditorTestCase() {
+    fun `test recursive`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command Recur1 Recur2"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command Recur2 Recur1"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("Recur1"))
+        assertPluginError(true) // Recursive command should error.
+    }
+
+    fun `test list aliases`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command"))
+        assertPluginError(false)
+        assertExOutput("Name        Args       Definition\n") // There should not be any aliases.
+
+        typeText(VimTestCase.commandToKeys("command Vs vs"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command Wq wq"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command WQ wq"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command-nargs=* Test1 echo"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command-nargs=? Test2 echo"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command-nargs=+ Test3 echo"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command-nargs=1 Test4 echo"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command"))
+        assertPluginError(false)
+        // The added alias should be listed
+        assertExOutput("""Name        Args       Definition
+            |Test1       *          echo
+            |Test2       ?          echo
+            |Test3       +          echo
+            |Test4       1          echo
+            |Vs          0          vs
+            |Wq          0          wq
+            |WQ          0          wq
+        """.trimMargin())
+
+        typeText(VimTestCase.commandToKeys("command W"))
+        assertPluginError(false)
+        // The filtered aliases should be listed
+        assertExOutput("""Name        Args       Definition
+            |Wq          0          wq
+            |WQ          0          wq
+        """.trimMargin())
+    }
+
+    fun `test bad alias`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("Vs"))
+        assertPluginError(true)
+        typeText(VimTestCase.commandToKeys("command Vs vs"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("Vs"))
+        assertPluginError(false)
+    }
+
+    fun `test lowercase should fail`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command lowercase vs"))
+        assertPluginError(true)
+    }
+
+    fun `test blacklisted alias should fail`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command X vs"))
+        assertPluginError(true)
+        typeText(VimTestCase.commandToKeys("command Next vs"))
+        assertPluginError(true)
+        typeText(VimTestCase.commandToKeys("command Print vs"))
+        assertPluginError(true)
+    }
+
+    fun `test add an existing alias and overwrite`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command Existing1 vs"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command Existing1 wq"))
+        assertPluginError(true)
+        typeText(VimTestCase.commandToKeys("command! Existing1 wq"))
+        assertPluginError(false)
+    }
+
+    fun `test add command with arguments`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command -nargs=* Error echo <args>"))
+        assertPluginError(false)
+    }
+
+    fun `test add command with arguments short`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command-nargs=* Error echo <args>"))
+        assertPluginError(false)
+    }
+
+    fun `test add command with arguments even shorter`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("com-nargs=* Error echo <args>"))
+        assertPluginError(false)
+    }
+
+    fun `test add command with various arguments`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command! -nargs=0 Error echo <args>"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command! -nargs=1 Error echo <args>"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command! -nargs=* Error echo <args>"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command! -nargs=? Error echo <args>"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command! -nargs=+ Error echo <args>"))
+        assertPluginError(false)
+    }
+
+    fun `test add command with invalid arguments`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command! -nargs= Error echo <args>"))
+        assertPluginError(true)
+        typeText(VimTestCase.commandToKeys("command! -nargs=-1 Error echo <args>"))
+        assertPluginError(true)
+        typeText(VimTestCase.commandToKeys("command! -nargs=# Error echo <args>"))
+        assertPluginError(true)
+    }
+
+    fun `test run command with arguments`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("let test = \"Hello!\""))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command! -nargs=1 Error echo <args>"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("Error test"))
+        assertPluginError(false)
+        assertExOutput("Hello!\n")
+
+        typeText(VimTestCase.commandToKeys("command! -nargs=1 Error echo <q-args>"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("Error test message"))
+        assertPluginError(false)
+        assertExOutput("test message\n")
+    }
+
+    fun `test run command that creates another command`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command! -nargs=1 CreateCommand command -nargs=1 <args> <lt>q-args>"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("CreateCommand Show echo"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("Show test"))
+        assertPluginError(false)
+        assertExOutput("test\n")
+    }
+
+    fun `test run command missing required argument`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command! -nargs=1 Error echo <q-args>"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("Error"))
+    }
+}
\ No newline at end of file
diff --git a/test/org/jetbrains/plugins/ideavim/ex/handler/DelCmdHandlerTest.kt b/test/org/jetbrains/plugins/ideavim/ex/handler/DelCmdHandlerTest.kt
new file mode 100644
index 000000000..74a7a613b
--- /dev/null
+++ b/test/org/jetbrains/plugins/ideavim/ex/handler/DelCmdHandlerTest.kt
@@ -0,0 +1,75 @@
+/*
+ * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
+ * Copyright (C) 2003-2019 The IdeaVim authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.jetbrains.plugins.ideavim.ex.handler
+import com.maddyhome.idea.vim.VimPlugin
+import org.jetbrains.plugins.ideavim.VimFileEditorTestCase
+import org.jetbrains.plugins.ideavim.VimTestCase
+
+/**
+ * @author Elliot Courant
+ */
+class DelCmdHandlerTest : VimFileEditorTestCase() {
+    fun `test remove alias`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("command"))
+        assertPluginError(false)
+        assertExOutput("Name        Args       Definition\n") // There should not be any aliases.
+
+        typeText(VimTestCase.commandToKeys("command Vs vs"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command Wq wq"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command WQ wq"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command"))
+        assertPluginError(false)
+        // The added alias should be listed
+        assertExOutput("""Name        Args       Definition
+            |Vs          0          vs
+            |Wq          0          wq
+            |WQ          0          wq
+        """.trimMargin())
+
+        typeText(VimTestCase.commandToKeys("command W"))
+        assertPluginError(false)
+        // The filtered aliases should be listed
+        assertExOutput("""Name        Args       Definition
+            |Wq          0          wq
+            |WQ          0          wq
+        """.trimMargin())
+
+        // Delete one of the aliases and then list all aliases again.
+        typeText(VimTestCase.commandToKeys("delcommand Wq"))
+        assertPluginError(false)
+        typeText(VimTestCase.commandToKeys("command"))
+        assertPluginError(false)
+        assertExOutput("""Name        Args       Definition
+            |Vs          0          vs
+            |WQ          0          wq
+        """.trimMargin())
+    }
+
+    fun `test remove non-existant alias`() {
+        VimPlugin.getCommand().resetAliases()
+        configureByText("\n")
+        typeText(VimTestCase.commandToKeys("delcommand VS"))
+        assertPluginError(true)
+    }
+}
\ No newline at end of file