text: # axf 09.02.2019
  # Updated to support vim-like keymaps
  # The idea is to switch keymap internally in Sublime text editor, not system-wide
  # Thus won't need to bloat Default.sublime-keymap file by duplicating layout-dependent keymaps 
  # Vim-based shortcuts and combinations will work naturally
  
  # Can specify several keymaps
  # Usage:
  # Put vim keymap files (i.e. russian-jcukenwin.vim, kazakh-jcuken.vim) into Vintage/keymap directory
  # Add "vintage_keymaps" parameter into user settings. The value of this parameter is a list of required keymaps
  # (i.e. "vintage_keymaps" : [ "russian-jcukenwin", "kazakh-jcuken" ])
  # Cycle through keymaps by CTRL-6; the keymap is off by default and is turned off again after list is exhausted
  
  # Limitations
  # CapsLock does not work properly
  # Autocomplete window is not opened automatically (still accessible by Alt-/)
  
  # TODO
  # Support "dead key" keymap, when several latin characters correspond to one mapped character
  # Eable character specification by keycode
  
  # Added registers window invoked by :reg
  
  import sublime, sublime_plugin
  import os.path
  from os.path import dirname, realpath
  import re
  
  # Normal: Motions apply to all the characters they select
  MOTION_MODE_NORMAL = 0
  # Used in visual line mode: Motions are extended to BOL and EOL.
  MOTION_MODE_LINE = 2
  
  # Registers are used for clipboards and macro storage
  g_registers = {}
  
  # Represents the current input state. The primary commands that interact with
  # this are:
  # * set_action
  # * set_motion
  # * push_repeat_digit
  class InputState:
      prefix_repeat_digits = []
      action_command = None
      action_command_args = None
      action_description = None
      motion_repeat_digits = []
      motion_command = None
      motion_command_args = None
      motion_mode = MOTION_MODE_NORMAL
      motion_mode_overridden = False
      motion_inclusive = False
      motion_clip_to_line = False
      register = None
  
  g_input_state = InputState()
  
  class MultiKeymapManager:
      def __init__(self):
          self.id = -1
          self.keymap = []
          self.current = None
      def add_keymap(self, name, keymap):
          self.keymap.append({'name' : name, 'keymap' : keymap})
      def purge_keymaps(self):
          self.keymap.clear()
      def get_name(self):
          return None if self.id == -1 else self.keymap[self.id]['name']
      def next_keymap(self):
          self.id+= 1
          if self.id == len(self.keymap):
              self.id = -1
              self.current = None
          else:
              self.current = self.keymap[self.id]['keymap']
      def map_char(self, c):
          return self.current.get(c, c) if self.current is not None else c
  
  g_keymap_manager = MultiKeymapManager()
  
  def parse_keymap_file(fname):
      # let b:keymap_name = "ru"
      name_re = re.compile('let\s*b\:keymap_name\s*=\s*"(.*?)"')
      # F А   CYRILLIC CAPITAL LETTER A
      # \"    Э   CYRILLIC CAPITAL LETTER E
      map_re = re.compile('^(\S*)\s*(\S*)\s*.*$')
  
      name = None
      keymap = {}
      try:
          with open(fname) as fin:
              for l in fin:
                 m = name_re.search(l)
                 if m is not None:
                      name = m.group(1)
                 m = map_re.match(l)
                 if m is not None:
                      keymap[m.group(1).lstrip('\\')] = m.group(2).lstrip('\\')
              return {'name' : name, 'keymap' : keymap}
      except:
          return None
  
  MM_PLUGIN_DIR = dirname(realpath(__file__))
  
  def load_keymaps():
      g_keymap_manager.purge_keymaps()
      settings = sublime.load_settings('Preferences.sublime-settings')
      keymap_names = settings.get('vintage_keymaps')
      if keymap_names is None or len(keymap_names) == 0:
          print('No keymaps specified')
          return
      for kn in keymap_names:
          full_fname = os.path.join(MM_PLUGIN_DIR, 'keymap', kn + '.vim')
          keymap = parse_keymap_file(full_fname)
          if keymap is None:
              print('No file for keymap "' + kn + '"')
              continue
          g_keymap_manager.add_keymap(keymap['name'], keymap['keymap'])
          print('keymap "' + keymap['name'] + '" loaded from ' + full_fname)
  
  # Updates the status bar to reflect the current mode and input state
  def update_status_line(view):
      desc = []
  
      if view.settings().get('command_mode'):
          if g_input_state.motion_mode == MOTION_MODE_LINE:
              desc = ['VISUAL LINE MODE']
          elif view.has_non_empty_selection_region():
              desc = ['VISUAL MODE']
          else:
              desc = ['COMMAND MODE']
              if g_input_state.action_command is not None:
                  if g_input_state.action_description:
                      desc.append(g_input_state.action_description)
                  else:
                      desc.append(g_input_state.action_command)
  
              repeat = (digits_to_number(g_input_state.prefix_repeat_digits)
                  * digits_to_number(g_input_state.motion_repeat_digits))
              if repeat != 1:
                  if g_input_state.action_command is not None:
                      desc[-1] += " * " + str(repeat)
                  else:
                      desc.append("* " + str(repeat))
  
          if g_input_state.register is not None:
              desc.insert(1, 'Register "' + g_input_state.register + '"')
      else:
          desc = ['INSERT MODE']
  
      keymap_name = g_keymap_manager.get_name()
      if keymap_name is not None:
          desc.append(keymap_name)
  
      view.set_status('mode', ' - '.join(desc))
  
  def set_motion_mode(view, mode):
      g_input_state.motion_mode = mode
      update_status_line(view)
  
  def reset_input_state(view, reset_motion_mode = True):
      global g_input_state
      g_input_state.prefix_repeat_digits = []
      g_input_state.action_command = None
      g_input_state.action_command_args = None
      g_input_state.action_description = None
      g_input_state.motion_repeat_digits = []
      g_input_state.motion_command = None
      g_input_state.motion_mode_overridden = False
      g_input_state.motion_command_args = None
      g_input_state.motion_inclusive = False
      g_input_state.motion_clip_to_line = False
      g_input_state.register = None
      if reset_motion_mode:
          set_motion_mode(view, MOTION_MODE_NORMAL)
  
  class ViCancelCurrentAction(sublime_plugin.TextCommand):
      def run(self, action, action_args = {}, motion_mode = None, description = None):
          reset_input_state(self.view, True)
  
  def string_to_motion_mode(mode):
      if mode == 'normal':
          return MOTION_MODE_NORMAL
      elif mode == 'line':
          return MOTION_MODE_LINE
      else:
          return -1
  
  # Called when the plugin is unloaded (e.g., perhaps it just got added to
  # ignored_packages). Ensure files aren't left in command mode.
  def plugin_unloaded():
      settings = sublime.load_settings('Preferences.sublime-settings')
      settings.clear_on_change('vintage_keymaps')
      for w in sublime.windows():
          for v in w.views():
              v.settings().set('command_mode', False)
              v.settings().set('inverse_caret_state', False)
              v.erase_status('mode')
  
  def plugin_loaded():
      load_keymaps()
      settings = sublime.load_settings('Preferences.sublime-settings')
      settings.add_on_change('vintage_keymaps', load_keymaps)
      for w in sublime.windows():
          for v in w.views():
              if v.settings().get("vintage_start_in_command_mode"):
                  v.settings().set('command_mode', True)
                  v.settings().set('inverse_caret_state', True)
              update_status_line(v)
  
  # Ensures the input state is reset when the view changes, or the user selects
  # with the mouse or non-vintage key bindings
  class InputStateTracker(sublime_plugin.EventListener):
      def on_activated(self, view):
          reset_input_state(view)
  
      def on_deactivated(self, view):
          reset_input_state(view)
  
          # Ensure that insert mode actions will no longer be grouped, otherwise
          # it can lead to the impression that too much is undone at once
          view.run_command('unmark_undo_groups_for_gluing')
  
      def on_post_save(self, view):
          # Ensure that insert mode actions will no longer be grouped, so it's
          # always possible to undo back to the last saved state
          view.run_command('unmark_undo_groups_for_gluing')
  
      def on_selection_modified(self, view):
          if not view.settings().get('command_mode'):
              return
          reset_input_state(view, False)
          # Get out of visual line mode if the selection has changed, e.g., due
          # to clicking with the mouse
          if (g_input_state.motion_mode == MOTION_MODE_LINE and
              not view.has_non_empty_selection_region()):
              g_input_state.motion_mode = MOTION_MODE_NORMAL
          update_status_line(view)
  
      def on_load(self, view):
          if view.settings().get("vintage_start_in_command_mode"):
              view.run_command('exit_insert_mode')
  
      def on_new(self, view):
          self.on_load(view)
  
      def on_clone(self, view):
          self.on_load(view)
  
      def on_query_context(self, view, key, operator, operand, match_all):
          if key == "vi_action" and g_input_state.action_command:
              if operator == sublime.OP_EQUAL:
                  return operand == g_input_state.action_command
              if operator == sublime.OP_NOT_EQUAL:
                  return operand != g_input_state.action_command
          elif key == "vi_has_action":
              v = g_input_state.action_command is not None
              if operator == sublime.OP_EQUAL: return v == operand
              if operator == sublime.OP_NOT_EQUAL: return v != operand
          elif key == "vi_has_register":
              r = g_input_state.register is not None
              if operator == sublime.OP_EQUAL: return r == operand
              if operator == sublime.OP_NOT_EQUAL: return r != operand
          elif key == "vi_motion_mode":
              m = string_to_motion_mode(operand)
              if operator == sublime.OP_EQUAL:
                  return m == g_input_state.motion_mode
              if operator == sublime.OP_NOT_EQUAL:
                  return m != g_input_state.motion_mode
          elif key == "vi_has_repeat_digit":
              if g_input_state.action_command:
                  v = len(g_input_state.motion_repeat_digits) > 0
              else:
                  v = len(g_input_state.prefix_repeat_digits) > 0
              if operator == sublime.OP_EQUAL: return v == operand
              if operator == sublime.OP_NOT_EQUAL: return v != operand
          elif key == "vi_has_input_state":
              v = (len(g_input_state.motion_repeat_digits) > 0 or
                  len(g_input_state.prefix_repeat_digits) > 0 or
                  g_input_state.action_command is not None or
                  g_input_state.register is not None)
              if operator == sublime.OP_EQUAL: return v == operand
              if operator == sublime.OP_NOT_EQUAL: return v != operand
          elif key == "vi_can_enter_text_object":
              v = (g_input_state.action_command is not None) or view.has_non_empty_selection_region()
              if operator == sublime.OP_EQUAL: return v == operand
              if operator == sublime.OP_NOT_EQUAL: return v != operand
  
          return None
  
  # Called when g_input_state represents a fully formed command. Generates a
  # call to vi_eval, which is what will be left on the undo/redo stack.
  def eval_input(view):
      global g_input_state
  
      cmd_args = {
          'action_command': g_input_state.action_command,
          'action_args': g_input_state.action_command_args,
          'motion_command': g_input_state.motion_command,
          'motion_args': g_input_state.motion_command_args,
          'motion_mode': g_input_state.motion_mode,
          'motion_inclusive': g_input_state.motion_inclusive,
          'motion_clip_to_line': g_input_state.motion_clip_to_line }
  
      if len(g_input_state.prefix_repeat_digits) > 0:
          cmd_args['prefix_repeat'] = digits_to_number(g_input_state.prefix_repeat_digits)
  
      if len(g_input_state.motion_repeat_digits) > 0:
          cmd_args['motion_repeat'] = digits_to_number(g_input_state.motion_repeat_digits)
  
      if g_input_state.register is not None:
          if not cmd_args['action_args']:
              cmd_args['action_args'] = {}
          cmd_args['action_args']['register'] = g_input_state.register
  
      reset_motion_mode = (g_input_state.action_command is not None)
  
      reset_input_state(view, reset_motion_mode)
  
      view.run_command('vi_eval', cmd_args)
  
  # Adds a repeat digit to the input state.
  # Repeat digits may come before the action, after the action, or both. For
  # example:
  #   4dw
  #   d4w
  #   2d2w
  # These commands will all delete 4 words.
  class PushRepeatDigit(sublime_plugin.TextCommand):
      def run(self, edit, digit):
          global g_input_state
          if g_input_state.action_command:
              g_input_state.motion_repeat_digits.append(digit)
          else:
              g_input_state.prefix_repeat_digits.append(digit)
          update_status_line(self.view)
  
  # Set the current action in the input state. Note that this won't create an
  # entry on the undo stack: only eval_input does this.
  class SetAction(sublime_plugin.TextCommand):
      # Custom version of run_, so an edit object isn't created. This allows
      # eval_input() to add the desired command to the undo stack
      def run_(self, edit_token, args):
          if 'event' in args:
              del args['event']
  
          return self.run(**args)
  
      def run(self, action, action_args = {}, description = None):
          global g_input_state
          g_input_state.action_command = action
          g_input_state.action_command_args = action_args
          g_input_state.action_description = description
  
          if self.view.has_non_empty_selection_region():
              # Currently in visual mode, so no following motion is expected:
              # eval the current input
              eval_input(self.view)
          else:
              update_status_line(self.view)
  
  def digits_to_number(digits):
      if len(digits) == 0:
          return 1
  
      number = 0
      place = 1
      for d in reversed(digits):
          number += place * int(d)
          place *= 10
      return number
  
  # Set the current motion in the input state. Note that this won't create an
  # entry on the undo stack: only eval_input does this.
  class SetMotion(sublime_plugin.TextCommand):
      # Custom version of run_, so an edit object isn't created. This allows
      # eval_input() to add the desired command to the undo stack
      def run_(self, edit_token, args):
          return self.run(**args)
  
      def run(self, motion, motion_args = {}, linewise = False, inclusive = False,
              clip_to_line = False, character = None, mode = None):
  
          global g_input_state
  
          # Pass the character, if any, onto the motion command.
          # This is required for 'f', 't', etc
          if character is not None:
              motion_args['character'] = g_keymap_manager.map_char(character)
  
          g_input_state.motion_command = motion
          g_input_state.motion_command_args = motion_args
          g_input_state.motion_inclusive = inclusive
          g_input_state.motion_clip_to_line = clip_to_line
          if not g_input_state.motion_mode_overridden \
                  and g_input_state.action_command \
                  and linewise:
              g_input_state.motion_mode = MOTION_MODE_LINE
  
          if mode is not None:
              m = string_to_motion_mode(mode)
              if m != -1:
                  set_motion_mode(self.view, m)
              else:
                  print("invalid motion mode:", mode)
  
          eval_input(self.view)
  
  # Run a single, combined action and motion. Examples are 'D' (delete to EOL)
  # and 'C' (change to EOL).
  class SetActionMotion(sublime_plugin.TextCommand):
      # Custom version of run_, so an edit object isn't created. This allows
      # eval_input() to add the desired command to the undo stack
      def run_(self, edit_token, args):
          return self.run(**args)
  
      def run(self, motion, action, motion_args = {}, motion_clip_to_line = False,
              motion_inclusive = False, motion_linewise = False, action_args = {}):
  
          global g_input_state
  
          g_input_state.motion_command = motion
          g_input_state.motion_command_args = motion_args
          g_input_state.motion_inclusive = motion_inclusive
          g_input_state.motion_clip_to_line = motion_clip_to_line
          g_input_state.action_command = action
          g_input_state.action_command_args = action_args
          if motion_linewise:
              g_input_state.motion_mode = MOTION_MODE_LINE
  
          eval_input(self.view)
  
  # Update the current motion mode. e.g., 'dvj'
  class SetMotionMode(sublime_plugin.TextCommand):
      def run_(self, edit_token, args):
          if 'event' in args:
              del args['event']
  
          return self.run(**args)
  
      def run(self, mode):
          global g_input_state
          m = string_to_motion_mode(mode)
  
          if m != -1:
              set_motion_mode(self.view, m)
              g_input_state.motion_mode_overridden = True
          else:
              print("invalid motion mode")
  
  class SetRegister(sublime_plugin.TextCommand):
      def run_(self, edit_token, args):
          return self.run(**args)
  
      def run(self, character):
          g_input_state.register = character
          update_status_line(self.view)
  
  def clip_point_to_line(view, f, pt):
      l = view.line(pt)
      if l.a == l.b:
          return l.a
  
      new_pt = f(pt)
      if new_pt < l.a:
          return l.a
      elif new_pt >= l.b:
          return l.b
      else:
          return new_pt
  
  def transform_selection(view, f, extend = False, clip_to_line = False):
      new_sel = []
      sel = view.sel()
      size = view.size()
  
      for r in sel:
          if clip_to_line:
              new_pt = clip_point_to_line(view, f, r.b)
          else:
              new_pt = f(r.b)
  
          if new_pt < 0: new_pt = 0
          elif new_pt > size: new_pt = size
  
          if extend:
              new_sel.append(sublime.Region(r.a, new_pt))
          else:
              new_sel.append(sublime.Region(new_pt))
  
      sel.clear()
      for r in new_sel:
          sel.add(r)
  
  def transform_selection_regions(view, f):
      new_sel = []
      sel = view.sel()
  
      for r in sel:
          nr = f(r)
          if nr is not None:
              new_sel.append(nr)
  
      sel.clear()
      for r in new_sel:
          sel.add(r)
  
  def expand_to_full_line(view, ignore_trailing_newline = True):
      new_sel = []
      for s in view.sel():
          if s.a == s.b:
              new_sel.append(view.full_line(s.a))
          else:
              la = view.full_line(s.begin())
              lb = view.full_line(s.end())
  
              a = la.a
  
              if ignore_trailing_newline and s.end() == lb.a:
                  # s.end() is already at EOL, don't go down to the next line
                  b = s.end()
              else:
                  b = lb.b
  
              if s.a < s.b:
                  new_sel.append(sublime.Region(a, b, 0))
              else:
                  new_sel.append(sublime.Region(b, a, 0))
  
      view.sel().clear()
      for s in new_sel:
          view.sel().add(s)
  
  def orient_single_line_region(view, forward, r):
      l = view.full_line(r.begin())
      if l.a == r.begin() and l.end() == r.end():
          if forward:
              return l
          else:
              return sublime.Region(l.b, l.a)
      else:
          return r
  
  def set_single_line_selection_direction(view, forward):
      transform_selection_regions(view,
          lambda r: orient_single_line_region(view, forward, r))
  
  def orient_single_character_region(view, forward, r):
      if r.begin() + 1 == r.end():
          if forward:
              return sublime.Region(r.begin(), r.end())
          else:
              return sublime.Region(r.end(), r.begin())
      else:
          return r
  
  def set_single_character_selection_direction(view, forward):
      transform_selection_regions(view,
          lambda r: orient_single_character_region(view, forward, r))
  
  def clip_empty_selection_to_line_contents(view):
      new_sel = []
      for s in view.sel():
          if s.empty():
              l = view.line(s.b)
              if s.b == l.b and not l.empty():
                  s = sublime.Region(l.b - 1, l.b - 1, s.xpos)
  
          new_sel.append(s)
  
      view.sel().clear()
      for s in new_sel:
          view.sel().add(s)
  
  def shrink_inclusive(r):
      if r.a < r.b:
          return sublime.Region(r.b - 1, r.b - 1, r.xpos)
      else:
          return sublime.Region(r.b, r.b, r.xpos)
  
  def shrink_exclusive(r):
      return sublime.Region(r.b, r.b, r.xpos)
  
  def shrink_to_first_char(r):
      if r.b < r.a:
          # If the Region is reversed, the first char is the character *before*
          # the first bound.
          return sublime.Region(r.a - 1)
      else:
          return sublime.Region(r.a)
  
  # This is the core: it takes a motion command, action command, and repeat
  # counts, and runs them all.
  #
  # Note that this doesn't touch g_input_state, and doesn't maintain any state
  # other than what's passed on its arguments. This allows it to operate correctly
  # in macros, and when running via repeat.
  class ViEval(sublime_plugin.TextCommand):
      def run_(self, edit_token, args):
          was_visual = self.view.has_non_empty_selection_region()
  
          edit = self.view.begin_edit(edit_token, self.name(), args)
          try:
              self.run(edit, **args)
          finally:
              self.view.end_edit(edit)
  
          # Glue the marked undo groups if visual mode was exited (e.g., by
          # running an action while in visual mode). This ensures that
          # v+motions+action can be repeated as a single unit.
          if self.view.settings().get('command_mode') == True:
              is_visual = self.view.has_non_empty_selection_region()
              if was_visual and not is_visual:
                  self.view.run_command('glue_marked_undo_groups')
              elif not is_visual:
                  self.view.run_command('unmark_undo_groups_for_gluing')
  
      def run(self, edit, action_command, action_args,
              motion_command, motion_args, motion_mode,
              motion_inclusive, motion_clip_to_line,
              prefix_repeat = None, motion_repeat = None):
  
          explicit_repeat = (prefix_repeat is not None or motion_repeat is not None)
  
          if prefix_repeat is None:
              prefix_repeat = 1
          if motion_repeat is None:
              motion_repeat = 1
  
          # Arguments are always passed as floats (thanks to JSON encoding),
          # convert them back to integers
          prefix_repeat = int(prefix_repeat)
          motion_repeat = int(motion_repeat)
          motion_mode = int(motion_mode)
  
          # Combine the prefix_repeat and motion_repeat into motion_repeat, to
          # allow commands like 2yy to work by first doing the motion twice,
          # then operating once
          if motion_command and prefix_repeat > 1:
              motion_repeat *= prefix_repeat
              prefix_repeat = 1
  
          # Check if the motion command would like to handle the repeat itself
          if motion_args and 'repeat' in motion_args:
              motion_args['repeat'] = motion_repeat * prefix_repeat
              motion_repeat = 1
              prefix_repeat = 1
  
          # Some commands behave differently if a repeat is given. e.g., 1G goes
          # to line one, but G without a repeat goes to EOF. Let the command
          # know if a repeat was specified.
          if motion_args and 'explicit_repeat' in motion_args:
              motion_args['explicit_repeat'] = explicit_repeat
  
          visual_mode = self.view.has_non_empty_selection_region()
  
          # Let the motion know if we're in visual mode, if it wants to know
          if motion_args and 'visual' in motion_args:
              motion_args['visual'] = visual_mode
  
          for i in range(prefix_repeat):
              # Run the motion command, extending the selection to the range of
              # characters covered by the motion
              if motion_command:
                  direction = 0
                  if motion_args and 'forward' in motion_args:
                      forward = motion_args['forward']
                      if forward:
                          direction = 1
                      else:
                          direction = -1
  
                  for j in range(motion_repeat):
                      if direction != 0 and motion_mode == MOTION_MODE_LINE:
                          # Ensure selections encompassing a single line are
                          # oriented in the same way as the motion, so they'll
                          # remain selected. This is needed so that Vk will work
                          # as expected
                          set_single_line_selection_direction(self.view, direction == 1)
                      elif direction != 0:
                          set_single_character_selection_direction(self.view, direction == 1)
  
                      if motion_mode == MOTION_MODE_LINE:
                          # Don't do either of the below things: this is
                          # important so that Vk on an empty line would select
                          # the following line.
                          pass
                      elif direction == 1 and motion_inclusive:
                          # Expand empty selections include the character
                          # they're on, and to start from the RHS of the
                          # character
                          transform_selection_regions(self.view,
                              lambda r: sublime.Region(r.b, r.b + 1, r.xpos) if r.empty() else r)
  
                      self.view.run_command(motion_command, motion_args)
  
              # If the motion needs to be clipped to the line, remove any
              # trailing newlines from the selection. For example, with the
              # caret at the start of the last word on the line, 'dw' should
              # delete the word, but not the newline, while 'w' should advance
              # the caret to the first character of the next line.
              if motion_mode != MOTION_MODE_LINE and action_command and motion_clip_to_line:
                  transform_selection_regions(self.view, lambda r: self.view.split_by_newlines(r)[0])
  
              reindent = False
  
              if motion_mode == MOTION_MODE_LINE:
                  expand_to_full_line(self.view, visual_mode)
                  if action_command == "enter_insert_mode":
                      # When lines are deleted before entering insert mode, the
                      # cursor should be left on an empty line. Leave the trailing
                      # newline out of the selection to allow for this.
                      transform_selection_regions(self.view,
                          lambda r: (sublime.Region(r.begin(), r.end() - 1)
                                     if not r.empty() and self.view.substr(r.end() - 1) == "\n"
                                     else r))
                      reindent = True
  
              if action_command:
                  # Apply the action to the selection
                  self.view.run_command(action_command, action_args)
                  if reindent and self.view.settings().get('auto_indent'):
                      self.view.run_command('reindent', {'force_indent': False})
  
          if not visual_mode:
              # Shrink the selection down to a point
              if motion_inclusive:
                  transform_selection_regions(self.view, shrink_inclusive)
              else:
                  transform_selection_regions(self.view, shrink_exclusive)
  
          # Clip the selections to the line contents
          if self.view.settings().get('command_mode'):
              clip_empty_selection_to_line_contents(self.view)
  
          # Ensure the selection is visible
          self.view.show(self.view.sel())
  
  
  class EnterInsertMode(sublime_plugin.TextCommand):
      # Ensure no undo group is created: the only entry on the undo stack should
      # be the insert_command, if any
      def run_(self, edit_token, args):
          if args:
              return self.run(**args)
          else:
              return self.run()
  
      def run(self, insert_command = None, insert_args = {}, register = '"'):
          # mark_undo_groups_for_gluing allows all commands run while in insert
          # mode to comprise a single undo group, which is important for '.' to
          # work as desired.
          self.view.run_command('maybe_mark_undo_groups_for_gluing')
          if insert_command:
              args = insert_args.copy()
              args.update({'register': register})
              self.view.run_command(insert_command, args)
  
          self.view.settings().set('command_mode', False)
          self.view.settings().set('inverse_caret_state', False)
          update_status_line(self.view)
  
  class ExitInsertMode(sublime_plugin.TextCommand):
      def run_(self, edit_token, args):
          edit = self.view.begin_edit(edit_token, self.name(), args)
          try:
              self.run(edit)
          finally:
              self.view.end_edit(edit)
  
          # Call after end_edit(), to ensure the final entry in the glued undo
          # group is 'exit_insert_mode'.
          self.view.run_command('glue_marked_undo_groups')
  
      def run(self, edit):
          self.view.settings().set('command_mode', True)
          self.view.settings().set('inverse_caret_state', True)
  
          if not self.view.has_non_empty_selection_region():
              self.view.run_command('vi_move_by_characters_in_line', {'forward': False})
  
          update_status_line(self.view)
  
  class EnterVisualMode(sublime_plugin.TextCommand):
      def run(self, edit):
          self.view.run_command('mark_undo_groups_for_gluing')
          if g_input_state.motion_mode != MOTION_MODE_NORMAL:
              set_motion_mode(self.view, MOTION_MODE_NORMAL)
  
          transform_selection_regions(self.view, lambda r: sublime.Region(r.b, r.b + 1) if r.empty() else r)
  
  class ExitVisualMode(sublime_plugin.TextCommand):
      def run(self, edit, toggle = False):
          if toggle:
              if g_input_state.motion_mode != MOTION_MODE_NORMAL:
                  set_motion_mode(self.view, MOTION_MODE_NORMAL)
              else:
                  self.view.run_command('shrink_selections')
          else:
              set_motion_mode(self.view, MOTION_MODE_NORMAL)
              self.view.run_command('shrink_selections')
  
          self.view.run_command('unmark_undo_groups_for_gluing')
  
  class EnterVisualLineMode(sublime_plugin.TextCommand):
      def run(self, edit):
          set_motion_mode(self.view, MOTION_MODE_LINE)
          expand_to_full_line(self.view)
          self.view.run_command('maybe_mark_undo_groups_for_gluing')
  
  class ShrinkSelections(sublime_plugin.TextCommand):
      def shrink(self, r):
          if r.empty():
              return r
          elif r.a < r.b:
              return sublime.Region(r.b - 1)
          else:
              return sublime.Region(r.b)
  
      def run(self, edit):
          transform_selection_regions(self.view, self.shrink)
  
  class ShrinkSelectionsToBeginning(sublime_plugin.TextCommand):
      def shrink(self, r):
          return sublime.Region(r.begin())
  
      def run(self, edit, register = '"'):
          transform_selection_regions(self.view, self.shrink)
  
  class ShrinkSelectionsToEnd(sublime_plugin.TextCommand):
      def shrink(self, r):
          end = r.end()
          if self.view.substr(end - 1) == u'\n':
              # For linewise selections put the cursor *before* the line break
              return sublime.Region(end - 1)
          else:
              return sublime.Region(end)
  
      def run(self, edit, register = '"'):
          transform_selection_regions(self.view, self.shrink)
  
  class VisualUpperCase(sublime_plugin.TextCommand):
      def run(self, edit):
          self.view.run_command("upper_case")
          self.view.run_command("exit_visual_mode")
  
  class VisualLowerCase(sublime_plugin.TextCommand):
      def run(self, edit):
          self.view.run_command("lower_case")
          self.view.run_command("exit_visual_mode")
  
  # Sequence is used as part of glue_marked_undo_groups: the marked undo groups
  # are rewritten into a single sequence command, that accepts all the previous
  # commands
  class Sequence(sublime_plugin.TextCommand):
      def run(self, edit, commands):
          for cmd, args in commands:
              self.view.run_command(cmd, args)
  
  class ViDelete(sublime_plugin.TextCommand):
      def run(self, edit, register = '"'):
          if self.view.has_non_empty_selection_region():
              set_register(self.view, register, forward=False)
              set_register(self.view, '1', forward=False)
              self.view.run_command('left_delete')
  
  class ViLeftDelete(sublime_plugin.TextCommand):
      def run(self, edit, register = '"'):
          set_register(self.view, register, forward=False)
          set_register(self.view, '1', forward=False)
          self.view.run_command('left_delete')
          clip_empty_selection_to_line_contents(self.view)
  
  class ViRightDelete(sublime_plugin.TextCommand):
      def run(self, edit, register = '"'):
          set_register(self.view, register, forward=True)
          set_register(self.view, '1', forward=True)
          self.view.run_command('right_delete')
          clip_empty_selection_to_line_contents(self.view)
  
  class ViCopy(sublime_plugin.TextCommand):
      def run(self, edit, register = '"'):
          set_register(self.view, register, forward=True)
          set_register(self.view, '0', forward=True)
          transform_selection_regions(self.view, shrink_to_first_char)
  
  class ViPrefixableCommand(sublime_plugin.TextCommand):
      # Ensure register and repeat are picked up from g_input_state, and that
      # it'll be recorded on the undo stack
      def run_(self, edit_token, args):
          if not args:
              args = {}
  
          if g_input_state.register:
              args['register'] = g_input_state.register
              g_input_state.register = None
  
          if g_input_state.prefix_repeat_digits:
              args['repeat'] = digits_to_number(g_input_state.prefix_repeat_digits)
              g_input_state.prefix_repeat_digits = []
  
          if 'event' in args:
              del args['event']
  
          edit = self.view.begin_edit(edit_token, self.name(), args)
          try:
              return self.run(edit, **args)
          finally:
              self.view.end_edit(edit)
  
  class ViPasteRight(ViPrefixableCommand):
      def advance(self, pt):
          if self.view.substr(pt) == '\n' or pt >= self.view.size():
              return pt
          else:
              return pt + 1
  
      def run(self, edit, register = '"', repeat = 1):
          visual_mode = self.view.has_non_empty_selection_region()
          if not visual_mode:
              transform_selection(self.view, lambda pt: self.advance(pt))
          self.view.run_command('paste_from_register', {'forward': not visual_mode,
                                                        'repeat': repeat,
                                                        'register': register})
  
  class ViPasteLeft(ViPrefixableCommand):
      def run(self, edit, register = '"', repeat = 1):
          self.view.run_command('paste_from_register', {'forward': False,
                                                        'repeat': repeat,
                                                        'register': register})
  
  def set_register(view, register, forward):
      delta = 1
      if not forward:
          delta = -1
  
      text = []
      regions = []
      for s in view.sel():
          if s.empty():
              s = sublime.Region(s.a, s.a + delta)
          text.append(view.substr(s))
          regions.append(s)
  
      text = '\n'.join(text)
  
      use_sys_clipboard = view.settings().get('vintage_use_clipboard', False) == True
  
      if (use_sys_clipboard and register == '"') or (register in ('*', '+')):
          sublime.set_clipboard(text)
          # If the system's clipboard is used, Vim always propagates the data to
          # the unnamed register too.
          register = '"'
  
      if register == '%':
          pass
      else:
          reg = register.lower()
          append = (reg != register)
  
          if append and reg in g_registers:
              g_registers[reg] += text
          else:
              g_registers[reg] = text
  
  def get_register(view, register):
      use_sys_clipboard = view.settings().get('vintage_use_clipboard', False) == True
      register = register.lower()
      if register == '%':
          if view.file_name():
              return os.path.basename(view.file_name())
          else:
              return None
      elif (use_sys_clipboard and register == '"') or (register in ('*', '+')):
          return sublime.get_clipboard()
      else:
          return g_registers.get(register, None)
  
  def has_register(register):
      if register in ['%', '*', '+']:
          return True
      else:
          return register in g_registers
  
  class PasteFromRegisterCommand(sublime_plugin.TextCommand):
      def run(self, edit, register, repeat = 1, forward = True):
          text = get_register(self.view, register)
          if not text:
              sublime.status_message("Undefined register" + register)
              return
          text = text * int(repeat)
  
          self.view.run_command('vi_delete')
  
          regions = [r for r in self.view.sel()]
          new_sel = []
  
          offset = 0
  
          for s in regions:
              s = sublime.Region(s.a + offset, s.b + offset)
  
              if len(text) > 0 and text[-1] == '\n':
                  # paste line-wise
                  if forward:
                      start = self.view.full_line(s.end()).b
                  else:
                      start = self.view.line(s.begin()).a
  
                  num = self.view.insert(edit, start, text)
                  new_sel.append(start)
              else:
                  # paste character-wise
                  num = self.view.insert(edit, s.begin(), text)
                  self.view.erase(edit, sublime.Region(s.begin() + num,
                      s.end() + num))
                  num -= s.size()
                  new_sel.append(s.begin())
  
              offset += num
  
          self.view.sel().clear()
          for s in new_sel:
              self.view.sel().add(s)
  
      def is_enabled(self, register, repeat = 1, forward = True):
          return has_register(register)
  
  class ReplaceCharacter(sublime_plugin.TextCommand):
      def run(self, edit, character):
          character = g_keymap_manager.map_char(character)
          new_sel = []
          created_new_line = False
          for s in reversed(self.view.sel()):
              if s.empty():
                  self.view.replace(edit, sublime.Region(s.b, s.b + 1), character)
                  if character == "\n":
                      created_new_line = True
                      # selection should be in the first column of the newly
                      # created line
                      new_sel.append(sublime.Region(s.b + 1))
                  else:
                      new_sel.append(s)
              else:
                  # Vim replaces characters with unprintable ones when r<enter> is
                  # pressed from visual mode.  Let's not make a replacement in
                  # that case.
                  if character != '\n':
                      # Process lines contained in the selection individually.
                      # This way we preserve newline characters.
                      lines = self.view.split_by_newlines(s)
                      for line in lines:
                          self.view.replace(edit, line, character * line.size())
                  new_sel.append(sublime.Region(s.begin()))
  
          self.view.sel().clear()
          for s in new_sel:
              self.view.sel().add(s)
  
          if created_new_line and self.view.settings().get('auto_indent'):
              self.view.run_command('reindent', {'force_indent': False})
  
  class CenterOnCursor(sublime_plugin.TextCommand):
      def run(self, edit):
          self.view.show_at_center(self.view.sel()[0])
  
  class ScrollCursorLineToTop(sublime_plugin.TextCommand):
      def run(self, edit):
          self.view.set_viewport_position((self.view.viewport_position()[0], self.view.layout_extent()[1]))
          self.view.show(self.view.sel()[0], False)
  
  class ScrollCursorLineToBottom(sublime_plugin.TextCommand):
      def run(self, edit):
          self.view.set_viewport_position((self.view.viewport_position()[0], 0.0))
          self.view.show(self.view.sel()[0], False)
  
  class ViScrollLines(ViPrefixableCommand):
      def run(self, edit, forward = True, repeat = None):
          if repeat:
              line_delta = repeat * (1 if forward else -1)
          else:
              viewport_height = self.view.viewport_extent()[1]
              lines_per_page = viewport_height / self.view.line_height()
              line_delta = int(round(lines_per_page / (2 if forward else -2)))
          visual_mode = self.view.has_non_empty_selection_region()
  
          y_deltas = []
          def transform(pt):
              row = self.view.rowcol(pt)[0]
              new_pt = self.view.text_point(row + line_delta, 0)
              y_deltas.append(self.view.text_to_layout(new_pt)[1]
                              - self.view.text_to_layout(pt)[1])
              return new_pt
  
          transform_selection(self.view, transform, extend = visual_mode)
  
          self.view.run_command('vi_move_to_first_non_white_space_character',
                                {'extend': visual_mode})
  
          # Vim scrolls the viewport as far as it moves the cursor.  With multiple
          # selections the cursors could have moved different distances, due to
          # word wrapping.  Move the viewport by the average of those distances.
          avg_y_delta = sum(y_deltas) / len(y_deltas)
          vp = self.view.viewport_position()
          self.view.set_viewport_position((vp[0], vp[1] + avg_y_delta))
  
  
  class ViIndent(sublime_plugin.TextCommand):
      def run(self, edit):
          self.view.run_command('indent')
          transform_selection_regions(self.view, shrink_to_first_char)
  
  class ViUnindent(sublime_plugin.TextCommand):
      def run(self, edit):
          self.view.run_command('unindent')
          transform_selection_regions(self.view, shrink_to_first_char)
  
  class ViSetBookmark(sublime_plugin.TextCommand):
      def run(self, edit, character):
          sublime.status_message("Set bookmark " + character)
          self.view.add_regions("bookmark_" + character, [s for s in self.view.sel()],
              "", "", sublime.PERSISTENT | sublime.HIDDEN)
  
  class ViSelectBookmark(sublime_plugin.TextCommand):
      def run(self, edit, character, select_bol=False):
          if self.view.get_regions("bookmark_" + character):
              self.view.run_command('select_all_bookmarks', {'name': "bookmark_" + character})
          if select_bol:
              sels = list(self.view.sel())
              self.view.sel().clear()
              for r in sels:
                  start = self.view.line(r.a).begin()
                  self.view.sel().add(sublime.Region(start, start))
  
  g_macro_target = None
  
  class ViBeginRecordMacro(sublime_plugin.TextCommand):
      def run(self, edit, character):
          global g_macro_target
          g_macro_target = character
          self.view.run_command('start_record_macro')
  
  class ViEndRecordMacro(sublime_plugin.TextCommand):
      def run(self, edit):
          self.view.run_command('stop_record_macro')
          if not g_macro_target:
              return
  
          m = sublime.get_macro()
          # TODO: Convert the macro to a string before trying to store it in a
          # register
          g_registers[g_macro_target] = m
  
  class ViReplayMacro(sublime_plugin.TextCommand):
      def run(self, edit, character):
          if not character in g_registers:
              return
          m = g_registers[character]
          global g_input_state
  
          prefix_repeat_digits, motion_repeat_digits = None, None
          if len(g_input_state.prefix_repeat_digits) > 0:
              prefix_repeat_digits = digits_to_number(g_input_state.prefix_repeat_digits)
  
          if len(g_input_state.motion_repeat_digits) > 0:
              motion_repeat_digits = digits_to_number(g_input_state.motion_repeat_digits)
  
          repetitions = 1
          if prefix_repeat_digits:
              repetitions *= prefix_repeat_digits
  
          if motion_repeat_digits:
              repetitions *= motion_repeat_digits
  
          for i in range(repetitions):
              for d in m:
                  cmd = d['command']
                  args = d['args']
                  self.view.run_command(cmd, args)
  
  class ShowAsciiInfo(sublime_plugin.TextCommand):
      def run(self, edit):
          c = self.view.substr(self.view.sel()[0].end())
          sublime.status_message("<%s> %d, Hex %s, Octal %s" %
                          (c, ord(c), hex(ord(c))[2:], oct(ord(c))))
  
  class ViReverseSelectionsDirection(sublime_plugin.TextCommand):
      def run(self, edit):
          new_sels = []
          for s in self.view.sel():
              new_sels.append(sublime.Region(s.b, s.a))
          self.view.sel().clear()
          for s in new_sels:
              self.view.sel().add(s)
  
  class MoveGroupFocus(sublime_plugin.WindowCommand):
      def run(self, direction):
          cells = self.window.get_layout()['cells']
          active_group = self.window.active_group()
          x1, y1, x2, y2 = cells[active_group]
  
          idxs = list(range(len(cells)))
          del idxs[active_group]
  
          # Matches are any group that shares a border with the active group in the
          # specified direction.
          if direction == "up":
              matches = (i for i in idxs if cells[i][3] == y1 and cells[i][0] < x2 and cells[i][2] > x1)
          elif direction == "down":
              matches = (i for i in idxs if cells[i][1] == y2 and cells[i][0] < x2 and cells[i][2] > x1)
          elif direction == "right":
              matches = (i for i in idxs if cells[i][0] == x2 and cells[i][1] < y2 and cells[i][3] > y1)
          elif direction == "left":
              matches = (i for i in idxs if cells[i][2] == x1 and cells[i][1] < y2 and cells[i][3] > y1)
  
          # Focus the first group found in the specified direction, if there is one.
          try:
              self.window.focus_group(next(matches))
          except StopIteration:
              return
  
  class PrintRegistersCommand(sublime_plugin.TextCommand):
      def run(self, edit):
          self.panel = self.view.window().create_output_panel('registers')
          self.view.window().run_command('show_panel', { 'panel': 'output.registers' })
          for name,contents in g_registers.items():
              for t in [('\n', '^M'), ('\t', '^I')]:
                  contents = contents.replace(t[0], t[1])
          self.panel.run_command('append', {'characters' : '{}: {}\n'.format(name, contents)})
  
  g_pairs = {
      '"' : '"',
      '\'' : '\'',
      '(' : ')',
      '{' : '}',
      '[' : ']',
      '<' : '>',
  }
  
  class InsertCharCommand(sublime_plugin.TextCommand):
      def run(self, edit, character):
          c = g_keymap_manager.map_char(character)
          for region in self.view.sel():
              if c in g_pairs and not region.empty():
                  self.view.insert(edit, region.begin(), c)
                  self.view.insert(edit, region.end()+1, g_pairs[c])
              else:
                  if not region.empty():
                      self.view.erase(edit, region)
                  self.view.insert(edit, region.begin(), c)
  
  class NextKeymapCommand(sublime_plugin.TextCommand):
       def run(self, edit):
          g_keymap_manager.next_keymap()
          update_status_line(self.view)
  