text: # -*- coding: utf-8 -*-
  #@+leo-ver=5-thin
  #@+node:ekr.20061031131434: * @file leoKeys.py
  #@@first
  """Gui-independent keystroke handling for Leo."""
  # pylint: disable=eval-used
  # pylint: disable=deprecated-method
      # The new methods may not exist in Python 2.
  #@+<< imports >>
  #@+node:ekr.20061031131434.1: ** << imports >> (leoKeys)
  import leo.core.leoGlobals as g
  import leo.commands.gotoCommands as gotoCommands
  import leo.external.codewise as codewise
  # import glob
  import inspect
  import os
  import re
  import string
  import sys
  import time
  assert time
  #@-<< imports >>
  #@+<< Key bindings, an overview >>
  #@+node:ekr.20130920121326.11281: ** << Key bindings, an overview >>
  #@@nocolor-node
  #@+at
  # 
  # The big pictures of key bindings:
  # 
  # 1. Code in leoKeys.py and in leoConfig.py converts user key settings to
  #    various Python **binding dictionaries** defined in leoKeys.py.
  # 
  # 2. An instance of LeoQtEventFilter should be attached to all visible panes
  #    in Leo's main window. g.app.gui.setFilter does this.
  # 
  # 3. LeoQtEventFilter.eventFilter calls k.masterKeyhandler for every
  #    keystroke. eventFilter passes only just the event argument to
  #    k.masterKeyHandler. The event arg gives both the widget in which the
  #    event occurs and the keystroke.
  # 
  # 4. k.masterKeyHandler and its helpers use the event argument and the
  #    binding dictionaries to execute the Leo command (if any) associated with
  #    the incoming keystroke.
  # 
  # Important details:
  # 
  # 1. g.app.gui.setFilter allows various traces and assertions to be made
  #    uniformly. The obj argument to setFilter is a QWidget object; the w
  #    argument to setFilter can be either the same as obj, or a Leo
  #    wrapper class. **Important**: the types of obj and w are not
  #    actually all that important, as discussed next.
  # 
  # 2. The logic in k.masterKeyHandler and its helpers is long and involved:
  # 
  # A. k.getPaneBinding associates a command with the incoming keystroke based
  #    on a) the widget's name and b) whether the widget is a text widget
  #    (which depends on the type of the widget).
  # 
  #    To do this, k.getPaneBinding uses a **binding priority table**. This
  #    table is defined within k.getPaneBinding itself. The table indicates
  #    which of several possible bindings should have priority. For instance,
  #    if the widget is a text widget, a user binding for a 'text' widget takes
  #    priority over a default key binding. Similarly, if the widget is Leo's
  #    tree widget, a 'tree' binding has top priority. There are many other
  #    details encapsulated in the table. The exactly details of the binding
  #    priority table are open to debate, but in practice the resulting
  #    bindings are as expeced.
  # 
  # B. If k.getPaneBinding finds a command associated with the incoming
  #    keystroke, k.masterKeyHandler calls k.masterCommand to execute the
  #    command. k.masterCommand handles many complex. See the source code for
  #    details.
  # 
  # C. If k.getPaneBinding fails to bind the incoming keystroke to a command,
  #    k.masterKeyHandler calls k.handleUnboundKeys to handle the keystroke.
  #    Depending on the widget, and settings, and the keystroke,
  #    k.handleUnboundKeys may do nothing, or it may call k.masterCommand to
  #    insert a plain key into the widget.
  #@-<< Key bindings, an overview >>
  #@+<< about 'internal' bindings >>
  #@+node:ekr.20061031131434.2: ** << about 'internal' bindings >>
  #@@nocolor
  #@+at
  # 
  # Here are the rules for translating key bindings (in leoSettings.leo)
  # into keys for k.bindingsDict:
  # 
  # 1. The case of plain letters is significant: a is not A.
  # 
  # 2. The Shift- prefix can be applied *only* to letters. Leo will ignore
  #    (with a warning) the shift prefix applied to any other binding,
  #    e.g., Ctrl-Shift-(
  # 
  # 3. The case of letters prefixed by Ctrl-, Alt-, Key- or Shift- is
  #    *not* significant. Thus, the Shift- prefix is required if you want
  #    an upper-case letter (with the exception of 'bare' uppercase
  #    letters.)
  # 
  # The following table illustrates these rules. In each row, the first
  # entry is the key (for k.bindingsDict) and the other entries are
  # equivalents that the user may specify in leoSettings.leo:
  # 
  # a, Key-a, Key-A
  # A, Shift-A
  # Alt-a, Alt-A
  # Alt-A, Alt-Shift-a, Alt-Shift-A
  # Ctrl-a, Ctrl-A
  # Ctrl-A, Ctrl-Shift-a, Ctrl-Shift-A
  # , Key-!,Key-exclam,exclam
  # 
  # This table is consistent with how Leo already works (because it is
  # consistent with Tk's key-event specifiers). It is also, I think, the
  # least confusing set of rules.
  #@-<< about 'internal' bindings >>
  #@+<< about key dicts >>
  #@+node:ekr.20061031131434.3: ** << about key dicts >>
  #@@nocolor
  #@+at
  # 
  # ivar                    Keys                Values
  # ----                    ----                ------
  # c.commandsDict          command names (1)   functions
  # k.bindingsDict          shortcuts           lists of BindingInfo objects
  # k.masterBindingsDict    scope names (2)     Interior masterBindingDicts (3)
  # k.masterGuiBindingsDict strokes             list of widgets in which stoke is bound
  # inverseBindingDict (5)  command names       lists of tuples (pane,key)
  # modeCommandsDict (6)    command name (7)    inner modeCommandsDicts (8)
  # 
  # New in Leo 4.7:
  # k.killedBindings is a list of command names for which bindings have been killed in local files.
  # 
  # Notes:
  # 
  # (1) Command names are minibuffer names (strings)
  # (2) Scope names are 'all','text',etc.
  # (3) Interior masterBindingDicts: Keys are strokes; values are BindingInfo objects.
  # (5) inverseBindingDict is **not** an ivar: it is computed by k.computeInverseBindingDict.
  # (6) A global dict: g.app.gui.modeCommandsDict
  # (7) enter-x-command
  # (8) Keys are command names, values are lists of BindingInfo objects.
  #@-<< about key dicts >>
  #@+others
  #@+node:ekr.20061031131434.4: ** class AutoCompleterClass
  class AutoCompleterClass:
      '''A class that inserts autocompleted and calltip text in text widgets.
      This class shows alternatives in the tabbed log pane.
  
      The keyHandler class contains hooks to support these characters:
      invoke-autocompleter-character (default binding is '.')
      invoke-calltips-character (default binding is '(')
      '''
      #@+others
      #@+node:ekr.20150509035140.1: *3* ac.cmd (decorator)
      def cmd(name):
          '''Command decorator for the AutoCompleter class.'''
          # pylint: disable=no-self-argument
          return g.new_cmd_decorator(name, ['c', 'k', 'autoCompleter'])
      #@+node:ekr.20061031131434.5: *3* ac.ctor & reloadSettings
      def __init__(self, k):
          '''Ctor for AutoCompleterClass class.'''
          # Ivars...
          self.c = k.c
          self.k = k
          self.force = None
          self.language = None
          self.namespaces = []
              # additional namespaces to search for objects, other code
              # can append namespaces to this to extend scope of search
          self.qw = None
              # The object that supports qcompletion methods.
          self.tabName = None
              # The name of the main completion tab.
          self.verbose = False
              # True: print all members, regardless of how many there are.
          self.w = None
              # The widget that gets focus after autocomplete is done.
          self.warnings = {}
              # Keys are language names.
          # Codewise pre-computes...
          self.codewiseSelfList = []
              # The (global) completions for "self."
          self.completionsDict = {}
              # Keys are prefixes, values are completion lists.
          self.reloadSettings()
          
      def reloadSettings(self):
          c = self.c
          self.auto_tab = c.config.getBool('auto-tab-complete', True)
          self.forbid_invalid = c.config.getBool('forbid-invalid-completions', False)
          self.use_jedi = c.config.getBool('use-jedi', False)
          self.use_qcompleter = c.config.getBool('use-qcompleter', False)
              # True: show results in autocompleter tab.
              # False: show results in a QCompleter widget.
      #@+node:ekr.20061031131434.8: *3* ac.Top level
      #@+node:ekr.20061031131434.9: *4* ac.autoComplete
      @cmd('auto-complete')
      def autoComplete(self, event=None, force=False):
          '''An event handler for autocompletion.'''
          c, k = self.c, self.k
          state = k.unboundKeyAction
          # pylint: disable=consider-using-ternary
          w = event and event.w or c.get_focus()
          self.force = force
          if state not in ('insert', 'overwrite'):
              return
          # First, handle the invocation character as usual.
          if not force:
              # Ctrl-period does *not* insert a period.
              k.masterCommand(event=event)
          # Allow autocompletion only in the body pane.
          if not c.widget_name(w).lower().startswith('body'):
              return
          self.language = g.scanForAtLanguage(c, c.p)
          if w and (k.enable_autocompleter or force): # self.language == 'python':
              self.w = w
              self.start(event)
      #@+node:ekr.20061031131434.10: *4* ac.autoCompleteForce
      @cmd('auto-complete-force')
      def autoCompleteForce(self, event=None):
          '''Show autocompletion, even if autocompletion is not presently enabled.'''
          return self.autoComplete(event, force=True)
      #@+node:ekr.20061031131434.12: *4* ac.enable/disable/toggleAutocompleter/Calltips
      @cmd('disable-autocompleter')
      def disableAutocompleter(self, event=None):
          '''Disable the autocompleter.'''
          self.k.enable_autocompleter = False
          self.showAutocompleterStatus()
  
      @cmd('disable-calltips')
      def disableCalltips(self, event=None):
          '''Disable calltips.'''
          self.k.enable_calltips = False
          self.showCalltipsStatus()
  
      @cmd('enable-autocompleter')
      def enableAutocompleter(self, event=None):
          '''Enable the autocompleter.'''
          self.k.enable_autocompleter = True
          self.showAutocompleterStatus()
  
      @cmd('enable-calltips')
      def enableCalltips(self, event=None):
          '''Enable calltips.'''
          self.k.enable_calltips = True
          self.showCalltipsStatus()
  
      @cmd('toggle-autocompleter')
      def toggleAutocompleter(self, event=None):
          '''Toggle whether the autocompleter is enabled.'''
          self.k.enable_autocompleter = not self.k.enable_autocompleter
          self.showAutocompleterStatus()
  
      @cmd('toggle-calltips')
      def toggleCalltips(self, event=None):
          '''Toggle whether calltips are enabled.'''
          self.k.enable_calltips = not self.k.enable_calltips
          self.showCalltipsStatus()
      #@+node:ekr.20061031131434.13: *4* ac.showCalltips
      @cmd('show-calltips')
      def showCalltips(self, event=None, force=False):
          '''Show the calltips at the cursor.'''
          c = self.c; k = c.k
          w = event and event.w
          if not w: return
          is_headline = c.widget_name(w).startswith('head')
          # Insert the calltip if possible, but not in headlines.
          if (k.enable_calltips or force) and not is_headline:
              self.w = w
              self.calltip()
          else:
              # Just insert the invocation character as usual.
              k.masterCommand(event=event)
      #@+node:ekr.20061031131434.14: *4* ac.showCalltipsForce
      @cmd('show-calltips-force')
      def showCalltipsForce(self, event=None):
          '''Show the calltips at the cursor, even if calltips are not presently enabled.'''
          return self.showCalltips(event, force=True)
      #@+node:ekr.20061031131434.15: *4* ac.showAutocompleter/CalltipsStatus
      def showAutocompleterStatus(self):
          '''Show the autocompleter status.'''
          k = self.k
          if not g.unitTesting:
              s = 'autocompleter %s' % (
                  'On' if k.enable_autocompleter else 'Off')
              g.red(s)
  
      def showCalltipsStatus(self):
          '''Show the autocompleter status.'''
          k = self.k
          if not g.unitTesting:
              s = 'calltips %s' % 'On' if k.enable_calltips else 'Off'
              g.red(s)
      #@+node:ekr.20061031131434.16: *3* ac.Helpers
      #@+node:ekr.20110512212836.14469: *4* ac.exit
      def exit(self):
  
          c = self.c
          w = self.w or c.frame.body.wrapper
          c.k.keyboardQuit()
          if self.use_qcompleter:
              if self.qw:
                  self.qw.end_completer()
                  self.qw = None # Bug fix: 2013/09/24.
          else:
              for name in (self.tabName, 'Modules', 'Info'):
                  c.frame.log.deleteTab(name)
          # Restore the selection range that may have been destroyed by changing tabs.
          c.widgetWantsFocusNow(w)
          i, j = w.getSelectionRange()
          w.setSelectionRange(i, j, insert=j)
          # Was in finish.
          c.frame.body.onBodyChanged('Typing')
          c.recolor()
  
      finish = exit
      abort = exit
      #@+node:ekr.20061031131434.18: *4* ac.append/begin/popTabName
      def appendTabName(self, word):
          self.setTabName(self.tabName + '.' + word)
  
      def beginTabName(self, word):
          self.setTabName('AutoComplete ' + word)
  
      def clearTabName(self):
          self.setTabName('AutoComplete ')
  
      def popTabName(self):
          s = self.tabName
          i = s.rfind('.', 0, -1)
          if i > -1:
              self.setTabName(s[0: i])
      # Underscores are not valid in Pmw tab names!
  
      def setTabName(self, s):
          c = self.c
          if self.tabName:
              c.frame.log.deleteTab(self.tabName)
          self.tabName = s.replace('_', '') or ''
          c.frame.log.clearTab(self.tabName)
      #@+node:ekr.20110509064011.14556: *4* ac.attr_matches
      def attr_matches(self, s, namespace):
          """Compute matches when string s is of the form name.name....name.
  
          Evaluates s using eval(s,namespace)
  
          Assuming the text is of the form NAME.NAME....[NAME], and is evaluatable in
          the namespace, it will be evaluated and its attributes (as revealed by
          dir()) are used as possible completions.
  
          For class instances, class members are are also considered.)
  
          **Warning**: this can still invoke arbitrary C code, if an object
          with a __getattr__ hook is evaluated.
  
          """
          # Seems to work great. Catches things like ''.<tab>
          m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", s)
          if not m:
              return []
          expr, attr = m.group(1, 3)
          try:
              safe_expr = self.strip_brackets(expr)
              obj = eval(safe_expr, namespace)
          except Exception:
              return []
          # Build the result.
          words = dir(obj)
          n = len(attr)
          result = ["%s.%s" % (expr, w) for w in words if w[: n] == attr]
          return result
      #@+node:ekr.20061031131434.11: *4* ac.auto_completer_state_handler
      def auto_completer_state_handler(self, event):
          '''Handle all keys while autocompleting.'''
          c, k, tag = self.c, self.k, 'auto-complete'
          state = k.getState(tag)
          ch = event.char if event else ''
          stroke = event.stroke if event else ''
          is_plain = k.isPlainKey(stroke)
          if state == 0:
              c.frame.log.clearTab(self.tabName)
              common_prefix, prefix, tabList = self.compute_completion_list()
              if tabList:
                  k.setState(tag, 1, handler=self.auto_completer_state_handler)
              else:
                  self.exit()
          elif ch in ('\n', 'Return'):
              self.exit()
          elif ch == 'Escape':
              self.exit()
          elif ch in ('\t', 'Tab'):
              self.compute_completion_list()
          elif ch in ('\b', 'BackSpace'):
              self.do_backspace()
          elif ch == '.':
              self.insert_string('.')
              self.compute_completion_list()
          elif ch == '?':
              self.info()
          elif ch == '!':
              # Toggle between verbose and brief listing.
              self.verbose = not self.verbose
              kind = 'ON' if self.verbose else 'OFF'
              message = 'verbose completions %s' % (kind)
              g.es_print(message)
              # This doesn't work because compute_completion_list clears the autocomplete tab.
              # self.put('', message, tabName=self.tabName)
              # This is almost invisible: the fg='red' is not honored.
              c.frame.putStatusLine(message, fg='red')
              self.compute_completion_list()
          # elif ch == 'Down' and hasattr(self,'onDown'):
              # self.onDown()
          # elif ch == 'Up' and hasattr(self,'onUp'):
              # self.onUp()
          elif is_plain and ch and ch in string.printable:
              self.insert_general_char(ch)
          elif stroke == k.autoCompleteForceKey:
              # This is probably redundant because completions will exist.
              # However, it doesn't hurt, and it may be useful rarely.
              common_prefix, prefix, tabList = self.compute_completion_list()
              if tabList:
                  self.show_completion_list(common_prefix, prefix, tabList)
              else:
                  g.warning('No completions')
                  self.exit()
          else:
              self.abort()
              return 'do-standard-keys'
          return None
      #@+node:ekr.20061031131434.20: *4* ac.calltip & helpers
      def calltip(self):
          '''Show the calltips for the present prefix.
          ch is '(' if the user has just typed it.
          '''
          obj, prefix = self.get_object()
          if obj:
              self.calltip_success(prefix, obj)
          else:
              self.calltip_fail(prefix)
          self.exit()
      #@+node:ekr.20110512090917.14468: *5* ac.calltip_fail
      def calltip_fail(self, prefix):
          '''Evaluation of prefix failed.'''
          self.insert_string('(')
      #@+node:ekr.20110512090917.14469: *5* ac.calltip_success
      def calltip_success(self, prefix, obj):
          try:
              # Get the parenthesized argument list.
              s1, s2, s3, s4 = inspect.getargspec(obj)
              s = inspect.formatargspec(s1, s2, s3, s4)
          except Exception:
              self.insert_string('(')
              return
          # Clean s and insert it: don't include the opening "(".
          if g.match(s, 1, 'self,'):
              s = s[6:].strip()
          elif g.match_word(s, 1, 'self'):
              s = s[5:].strip()
          else:
              s = s[1:].strip()
          self.insert_string("(", select=False)
          self.insert_string(s, select=True)
      #@+node:ekr.20061031131434.28: *4* ac.compute_completion_list & helper
      def compute_completion_list(self):
          '''Return the autocompleter completion list.'''
          prefix = self.get_autocompleter_prefix()
          key, options = self.get_cached_options(prefix)
          if not options:
              options = self.get_completions(prefix)
          tabList, common_prefix = g.itemsMatchingPrefixInList(
              prefix, options, matchEmptyPrefix=False)
          if not common_prefix:
              tabList, common_prefix = g.itemsMatchingPrefixInList(
                  prefix, options, matchEmptyPrefix=True)
          if tabList:
              self.show_completion_list(common_prefix, prefix, tabList)
          return common_prefix, prefix, tabList
      #@+node:ekr.20110514051607.14524: *5* ac.get_cached_options
      def get_cached_options(self, prefix):
          d = self.completionsDict
          # Search the completions Dict for shorter and shorter prefixes.
          i = len(prefix)
          while i > 0:
              key = prefix[: i]
              i -= 1
              # Make sure we report hits only of real objects.
              if key.endswith('.'):
                  return key, []
              options = d.get(key)
              if options:
                  return key, options
          return None, []
      #@+node:ekr.20061031131434.29: *4* ac.do_backspace
      def do_backspace(self):
          '''Delete the character and recompute the completion list.'''
          c, w = self.c, self.w
          c.bodyWantsFocusNow()
          i = w.getInsertPoint()
          if i <= 0:
              self.exit()
              return
          w.delete(i - 1, i)
          w.setInsertPoint(i - 1)
          if i <= 1:
              self.exit()
          else:
              # Update the list. Abort if there is no prefix.
              common_prefix, prefix, tabList = self.compute_completion_list()
              if not prefix:
                  self.exit()
      #@+node:ekr.20110510133719.14548: *4* ac.do_qcompleter_tab (not used)
      def do_qcompleter_tab(self, prefix, options):
          '''Return the longest common prefix of all the options.'''
          matches, common_prefix = g.itemsMatchingPrefixInList(
              prefix, options, matchEmptyPrefix=False)
          return common_prefix
      #@+node:ekr.20110509064011.14561: *4* ac.get_autocompleter_prefix
      def get_autocompleter_prefix(self):
          # Only the body pane supports auto-completion.
          w = self.c.frame.body.wrapper
          s = w.getAllText()
          if not s: return ''
          i = w.getInsertPoint() - 1
          i = j = max(0, i)
          while i >= 0 and (s[i].isalnum() or s[i] in '._'):
              i -= 1
          i += 1
          j += 1
          prefix = s[i: j]
          return prefix
      #@+node:ekr.20110512212836.14471: *4* ac.get_completions & helpers
      jedi_warning = False
  
      def get_completions(self, prefix):
          '''Return jedi or codewise completions.'''
          d = self.completionsDict
          if self.use_jedi:
              try:
                  import jedi
              except ImportError:
                  if not self.jedi_warning:
                      self.jedi_warning = False
                      g.es_print('can not import jedi')
                      g.es_print('ignoring @bool use_jedi = True')
              if jedi:
                  aList = (
                      self.get_jedi_completions(prefix) or
                          # Prefer the jedi completions.
                      self.get_leo_completions(prefix))
                  d[prefix] = aList
                  return aList
          #
          # Not jedi. Use codewise.
          # Precompute the codewise completions for '.self'.
          if not self.codewiseSelfList:
              aList = self.get_codewise_completions('self.')
              self.codewiseSelfList = [z[5:] for z in aList]
              d['self.'] = self.codewiseSelfList
          # Use the cached list if it exists.
          aList = d.get(prefix)
          if aList:
              return aList
          aList = (
              self.get_leo_completions(prefix) or
                  # Prefer the Leo completions.
              self.get_codewise_completions(prefix)
          )
          d[prefix] = aList
          return aList
      #@+node:ekr.20110510120621.14539: *5* ac.get_codewise_completions & helpers
      def get_codewise_completions(self, prefix):
          '''Use codewise to generate a list of hits.'''
          c = self.c
          m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", prefix)
          if m:
              varname = m.group(1)
              ivar = m.group(3)
              kind, aList = self.guess_class(c, varname)
          else:
              kind, aList = 'none', []
              varname, ivar = None, None
          if aList:
              if kind == 'class':
                  hits = self.lookup_methods(aList, ivar)
                  hits.extend(self.codewiseSelfList)
              elif kind == 'module':
                  hits = self.lookup_modules(aList, ivar)
          else:
              aList2 = prefix.split('.')
              if aList2:
                  func = aList2[-1]
                  hits = self.lookup_functions(func)
              else:
                  hits = []
          if 1: # A kludge: add the prefix to each hit.
              hits = ['%s.%s' % (varname, z) for z in hits]
          return hits
      #@+node:ekr.20110510120621.14540: *6* ac.clean
      def clean(self, hits):
          '''Clean up hits, a list of ctags patterns, for use in completion lists.'''
          # Just take the function name: ignore the signature & file.
          aList = list(set([z[0] for z in hits]))
          aList.sort()
          return aList
      #@+node:ekr.20110512232915.14481: *6* ac.clean_for_display (not used)
      def clean_for_display(self, hits):
          '''Clean up hits, a list of ctags patterns, for display purposes.'''
          aList = []
          for h in hits:
              s = h[0]
              # Display oriented: no good for completion list.
              fn = h[1].strip()
              if fn.startswith('/'):
                  sig = fn[2: -4].strip()
              else:
                  sig = fn
              aList.append('%s: %s' % (s, sig))
          aList = list(set(aList))
          aList.sort()
          return aList
      #@+node:ekr.20110510120621.14542: *6* ac.guess_class
      def guess_class(self, c, varname):
          '''Return kind, class_list'''
          # if varname == 'g':
              # return 'module',['leoGlobals']
          if varname == 'p':
              return 'class', ['position']
          if varname == 'c':
              return 'class', ['Commands']
          if varname == 'self':
              # Return the nearest enclosing class.
              for p in c.p.parents():
                  h = p.h
                  # pylint: disable=anomalous-backslash-in-string
                  m = re.search('class\s+(\w+)', h)
                  if m:
                      return 'class', [m.group(1)]
          if 1:
              aList = []
          else:
              # This is not needed now that we add the completions for 'self'.
              aList = ContextSniffer().get_classes(c.p.b, varname)
          return 'class', aList
      #@+node:ekr.20110510120621.14543: *6* ac.lookup_functions/methods/modules
      def lookup_functions(self, prefix):
          aList = codewise.cmd_functions([prefix])
          hits = [z.split(None, 1) for z in aList if z.strip()]
          return self.clean(hits)
  
      def lookup_methods(self, aList, prefix): # prefix not used, only aList[0] used.
          aList = codewise.cmd_members([aList[0]])
          hits = [z.split(None, 1) for z in aList if z.strip()]
          return self.clean(hits)
  
      def lookup_modules(self, aList, prefix): # prefix not used, only aList[0] used.
          aList = codewise.cmd_functions([aList[0]])
          hits = [z.split(None, 1) for z in aList if z.strip()]
          return self.clean(hits)
      #@+node:ekr.20180519111302.1: *5* ac.get_jedi_completions & helper
      def get_jedi_completions(self, prefix):
  
          c = self.c
          w = c.frame.body.wrapper
          i = w.getInsertPoint()
          p = c.p
          body_s = p.b
          #
          # Get the entire source for jedi.
          t1 = time.process_time()
          goto = gotoCommands.GoToCommands(c)
          root, fileName = goto.find_root(p)
          if root:
              source = goto.get_external_file_with_sentinels(root=root or p)
              n0 = goto.find_node_start(p=p, s=source)
              if n0 is None: n0 = 0
          else:
              source = body_s
              n0 = 0
          t2 = time.process_time()
          #
          # Get local line
          lines = g.splitLines(body_s)
          row, column = g.convertPythonIndexToRowCol(body_s, i)
          line = lines[row]
          #
          # Find the global line, and compute offsets.
          source_lines = g.splitLines(source)
          for jedi_line, g_line in enumerate(source_lines[n0:]):
              if line.lstrip() == g_line.lstrip():
                  # Adjust the column.
                  indent1 = len(line)-len(line.lstrip())
                  indent2 = len(g_line)-len(g_line.lstrip())
                  if indent2 >= indent1:
                      local_column = column # For traces.
                      column += abs(indent2-indent1)
                      break
          else:
              completions = None
              jedi_line, indent1, indent2 = None, None, None
              g.printObj(source_lines[n0-1:n0+30])
              print('can not happen: not found: %r' % line)
          #
          # Get the jedi completions.
          if jedi_line is not None:
              import jedi
              try:
                  script = jedi.Script(
                      source=source,
                      line=1+n0+jedi_line,
                      column=column,
                      path = g.shortFileName(fileName),
                      # encoding='utf-8',
                      # sys_path=None):
                  ) 
                  completions = script.completions()
                  t3 = time.process_time()
              except ValueError:
                  t3 = time.process_time()
                  completions = None
                  g.printObj(source_lines[n0-1:n0+30])
                  print('ERROR', p.h)
          if not completions:
              return []
          # May be used in traces below.
          assert t3 >= t2 >= t1
          assert local_column is not None  
          completions = [z.name for z in completions]
          completions = [self.add_prefix(prefix, z) for z in completions]
          # Retain these for now...
              # g.printObj(completions[:5])
              # head = line[:local_column]
              # ch = line[local_column:local_column+1]
              # g.trace(len(completions), repr(ch), head.strip())
          return completions
      #@+node:ekr.20180526211127.1: *6* ac.add_prefix
      def add_prefix(self, prefix, s):
          '''A hack to match the callers expectations.'''
          if prefix.find('.') > -1:
              aList = prefix.split('.')
              prefix = '.'.join(aList[:-1]) + '.'
          return s if s.startswith(prefix) else prefix + s
      #@+node:ekr.20110509064011.14557: *5* ac.get_leo_completions
      def get_leo_completions(self, prefix):
          '''Return completions in an environment defining c, g and p.'''
          aList = []
          for d in self.namespaces + [self.get_leo_namespace(prefix)]:
              aList.extend(self.attr_matches(prefix, d))
          aList.sort()
          return aList
      #@+node:ekr.20110512090917.14466: *4* ac.get_leo_namespace
      def get_leo_namespace(self, prefix):
          '''
          Return an environment in which to evaluate prefix.
          Add some common standard library modules as needed.
          '''
          k = self.k
          d = {'c': k.c, 'p': k.c.p, 'g': g}
          aList = prefix.split('.')
          if len(aList) > 1:
              name = aList[0]
              m = sys.modules.get(name)
              if m:
                  d[name] = m
          return d
      #@+node:ekr.20110512170111.14472: *4* ac.get_object
      def get_object(self):
          '''Return the object corresponding to the current prefix.'''
          common_prefix, prefix1, aList = self.compute_completion_list()
          if not aList:
              return None, prefix1
          if len(aList) == 1:
              prefix = aList[0]
          else:
              prefix = common_prefix
          if prefix.endswith('.') and self.use_qcompleter:
              prefix += self.qcompleter.get_selection()
          safe_prefix = self.strip_brackets(prefix)
          for d in self.namespaces + [self.get_leo_namespace(prefix)]:
              try:
                  obj = eval(safe_prefix, d)
                  break # only reached if none of the exceptions below occur
              except AttributeError:
                  obj = None
              except NameError:
                  obj = None
              except SyntaxError:
                  obj = None
              except Exception:
                  g.es_exception()
                  obj = None
          return obj, prefix
      #@+node:ekr.20061031131434.38: *4* ac.info
      def info(self):
          '''Show the docstring for the present completion.'''
          c = self.c
          obj, prefix = self.get_object()
          c.frame.log.clearTab('Info', wrap='word')
          put = lambda s: self.put('', s, tabName='Info')
          put(prefix)
          try:
              argspec = inspect.getargspec(obj)
              # uses None instead of empty list
              argn = len(argspec.args or [])
              defn = len(argspec.defaults or [])
              put("args:")
              simple_args = argspec.args[: argn - defn]
              if not simple_args:
                  put('    (none)')
              else:
                  put('    ' + ', '.join(' ' + i for i in simple_args))
              put("keyword args:")
              if not argspec.defaults:
                  put('    (none)')
              for i in range(defn):
                  arg = argspec.args[-defn + i]
                  put("    %s = %s" % (arg, repr(argspec.defaults[i])))
              if argspec.varargs:
                  put("varargs: *" + argspec.varargs)
              if argspec.keywords:
                  put("keywords: **" + argspec.keywords)
              put('\n') # separate docstring
          except TypeError:
              put('\n') # not a callable
          doc = inspect.getdoc(obj)
          put(doc if doc else "No docstring for " + repr(prefix))
      #@+node:ekr.20110510071925.14586: *4* ac.init_qcompleter
      def init_qcompleter(self, event=None):
  
          # Compute the prefix and the list of options.
          prefix = self.get_autocompleter_prefix()
          options = self.get_completions(prefix)
          w = self.c.frame.body.wrapper.widget
              # A LeoQTextBrowser.  May be none for unit tests.
          if w and options:
              self.qw = w
              self.qcompleter = w.init_completer(options)
              self.auto_completer_state_handler(event)
          else:
              if not g.unitTesting:
                  g.warning('No completions')
              self.exit()
      #@+node:ekr.20110511133940.14552: *4* ac.init_tabcompleter
      def init_tabcompleter(self, event=None):
          # Compute the prefix and the list of options.
          prefix = self.get_autocompleter_prefix()
          options = self.get_completions(prefix)
          if options:
              self.clearTabName() # Creates the tabbed pane.
              self.auto_completer_state_handler(event)
          else:
              g.warning('No completions')
              self.exit()
      #@+node:ekr.20061031131434.39: *4* ac.insert_general_char
      def insert_general_char(self, ch):
  
          k, w = self.k, self.w
          if g.isWordChar(ch):
              self.insert_string(ch)
              common_prefix, prefix, aList = self.compute_completion_list()
              if not aList:
                  if self.forbid_invalid:
                      # Delete the character we just inserted.
                      self.do_backspace()
              # @bool auto_tab_complete is deprecated.
              # Auto-completion makes no sense if it is False.
              elif self.auto_tab and len(common_prefix) > len(prefix):
                  extend = common_prefix[len(prefix):]
                  ins = w.getInsertPoint()
                  w.insert(ins, extend)
          else:
              if ch == '(' and k.enable_calltips:
                  # This calls self.exit if the '(' is valid.
                  self.calltip()
              else:
                  self.insert_string(ch)
                  self.exit()
      #@+node:ekr.20061031131434.31: *4* ac.insert_string
      def insert_string(self, s, select=False):
          '''Insert s at the insertion point.'''
          c = self.c
          w = self.w
          if not g.isTextWrapper(w): # Bug fix: 2016/10/29.
              return
          c.widgetWantsFocusNow(w)
          i = w.getInsertPoint()
          w.insert(i, s)
          if select:
              j = i + len(s)
              w.setSelectionRange(i, j, insert=j)
          c.frame.body.onBodyChanged('Typing')
          if self.use_qcompleter:
              if self.qw:
                  c.widgetWantsFocusNow(self.qw.leo_qc)
      #@+node:ekr.20110314115639.14269: *4* ac.is_leo_source_file
      def is_leo_source_file(self):
          '''Return True if this is one of Leo's source files.'''
          c = self.c
          table = (z.lower() for z in (
              'leoDocs.leo',
              'LeoGui.leo', 'LeoGuiPluginsRef.leo',
              'leoPlugins.leo', 'leoPluginsRef.leo',
              'leoPy.leo', 'leoPyRef.leo',
              'myLeoSettings.leo', 'leoSettings.leo',
              'ekr.leo',
              # 'test.leo',
          ))
          return c.shortFileName().lower() in table
      #@+node:ekr.20101101175644.5891: *4* ac.put
      def put(self, *args, **keys):
          '''Put s to the given tab.
  
          May be overridden in subclasses.'''
          # print('autoCompleter.put',args,keys)
          if g.unitTesting:
              pass
          else:
              g.es(*args, **keys)
      #@+node:ekr.20110511133940.14561: *4* ac.show_completion_list & helpers
      def show_completion_list(self, common_prefix, prefix, tabList):
          c = self.c
          aList = common_prefix.split('.')
          header = '.'.join(aList[: -1])
          # "!" toggles self.verbose.
          if self.verbose or self.use_qcompleter or len(tabList) < 20:
              tabList = self.clean_completion_list(header, tabList,)
          else:
              tabList = self.get_summary_list(header, tabList)
          if self.use_qcompleter:
              # Put the completions in the QListView.
              if self.qw:
                  self.qw.show_completions(tabList)
          else:
              # Update the tab name, creating the tab if necessary.
              c.widgetWantsFocus(self.w)
              c.frame.log.clearTab(self.tabName)
              self.beginTabName(header + '.' if header else '')
              s = '\n'.join(tabList)
              self.put('', s, tabName=self.tabName)
      #@+node:ekr.20110513104728.14453: *5* ac.clean_completion_list
      def clean_completion_list(self, header, tabList):
          '''Return aList with header removed from the start of each list item.'''
          return [
              z[len(header) + 1:] if z.startswith(header) else z
                  for z in tabList]
      #@+node:ekr.20110513104728.14454: *5* ac.get_summary_list
      def get_summary_list(self, header, tabList):
          '''Show the possible starting letters,
          but only if there are more than one.
          '''
          d = {}
          for z in tabList:
              tail = z[len(header):] if z else ''
              if tail.startswith('.'): tail = tail[1:]
              ch = tail[0] if tail else ''
              if ch:
                  n = d.get(ch, 0)
                  d[ch] = n + 1
          aList = ['%s %d' % (ch2, d.get(ch2)) for ch2 in sorted(d)]
          if len(aList) > 1:
              tabList = aList
          else:
              tabList = self.clean_completion_list(header, tabList)
          return tabList
      #@+node:ekr.20061031131434.46: *4* ac.start
      def start(self, event):
          # We don't need to clear this now that we don't use ContextSniffer.
          c = self.c
          if c.config.getBool('use-jedi', default=True):
              self.completionsDict = {}
          if self.use_qcompleter:
              self.init_qcompleter(event)
          else:
              self.init_tabcompleter(event)
      #@+node:ekr.20110512170111.14471: *4* ac.strip_brackets
      def strip_brackets(self, s):
          '''Return s with all brackets removed.
  
          This (mostly) ensures that eval will not execute function calls, etc.
          '''
          for ch in '[]{}()':
              s = s.replace(ch, '')
          return s
      #@-others
  #@+node:ekr.20110312162243.14260: ** class ContextSniffer
  class ContextSniffer:
      """ Class to analyze surrounding context and guess class
  
      For simple dynamic code completion engines.
      """
  
      def __init__(self):
          self.vars = {}
              # Keys are var names; values are list of classes
      #@+others
      #@+node:ekr.20110312162243.14261: *3* get_classes
      def get_classes(self, s, varname):
          '''Return a list of classes for string s.'''
          self.push_declarations(s)
          aList = self.vars.get(varname, [])
          return aList
      #@+node:ekr.20110312162243.14262: *3* set_small_context
      # def set_small_context(self, body):
          # """ Set immediate function """
          # self.push_declarations(body)
      #@+node:ekr.20110312162243.14263: *3* push_declarations & helper
      def push_declarations(self, s):
          for line in s.splitlines():
              line = line.lstrip()
              if line.startswith('#'):
                  line = line.lstrip('#')
                  parts = line.split(':')
                  if len(parts) == 2:
                      a, b = parts
                      self.declare(a.strip(), b.strip())
      #@+node:ekr.20110312162243.14264: *4* declare
      def declare(self, var, klass):
          vars = self.vars.get(var, [])
          if not vars:
              self.vars[var] = vars
          vars.append(klass)
      #@-others
  #@+node:ekr.20140813052702.18194: ** class FileNameChooser
  class FileNameChooser:
      '''A class encapsulation file selection & completion logic.'''
      #@+others
      #@+node:ekr.20140813052702.18195: *3* fnc.__init__
      def __init__(self, c):
          '''Ctor for FileNameChooser class.'''
          self.c = c
          self.k = c.k
          assert c and c.k
          self.log = c.frame.log or g.NullObject()
          self.callback = None
          self.filterExt = None
          self.log = None # inited later.
          self.prompt = None
          self.tabName = None
      #@+node:ekr.20140813052702.18196: *3* fnc.compute_tab_list
      def compute_tab_list(self):
          '''Compute the list of completions.'''
          path = self.get_label()
          # #215: insert-file-name doesn't process ~
          path = g.os_path_expanduser(path)
          sep = os.path.sep
          if g.os_path_exists(path):
              if g.os_path_isdir(path):
                  if path.endswith(os.sep):
                      aList = g.glob_glob(path + '*')
                  else:
                      aList = g.glob_glob(path + sep + '*')
                  tabList = [z + sep if g.os_path_isdir(z) else z for z in aList]
              else:
                  # An existing file.
                  tabList = [path]
          else:
              if path and path.endswith(sep):
                  path = path[: -1]
              aList = g.glob_glob(path + '*')
              tabList = [z + sep if g.os_path_isdir(z) else z for z in aList]
          if self.filterExt:
              for ext in self.filterExt:
                  tabList = [z for z in tabList if not z.endswith(ext)]
          tabList = [g.os_path_normslashes(z) for z in tabList]
          junk, common_prefix = g.itemsMatchingPrefixInList(path, tabList)
          return common_prefix, tabList
      #@+node:ekr.20140813052702.18197: *3* fnc.do_back_space
      def do_back_space(self):
          '''Handle a back space.'''
          w = self.c.k.w
          if w and w.hasSelection():
              # s = w.getAllText()
              i, j = w.getSelectionRange()
              w.delete(i, j)
              s = self.get_label()
          else:
              s = self.get_label()
              if s:
                  s = s[: -1]
              self.set_label(s)
          if s:
              common_prefix, tabList = self.compute_tab_list()
              # Do *not* extend the label to the common prefix.
          else:
              tabList = []
          self.show_tab_list(tabList)
      #@+node:ekr.20140813052702.18198: *3* fnc.do_char
      def do_char(self, char):
          '''Handle a non-special character.'''
          w = self.c.k.w
          if w and w.hasSelection:
              # s = w.getAllText()
              i, j = w.getSelectionRange()
              w.delete(i, j)
              w.setInsertPoint(i)
              w.insert(i, char)
          else:
              self.extend_label(char)
          common_prefix, tabList = self.compute_tab_list()
          self.show_tab_list(tabList)
          if common_prefix:
              if 0:
                  # This is a bit *too* helpful.
                  # It's too easy to type ahead by mistake.
                  # Instead, completion should happen only when the user types <tab>.
                  self.set_label(common_prefix)
              # Recompute the tab list.
              common_prefix, tabList = self.compute_tab_list()
              self.show_tab_list(tabList)
              if len(tabList) == 1:
                  # Automatically complete the typing only if there is only one item in the list.
                  self.set_label(common_prefix)
          else:
              # Restore everything.
              self.set_label(self.get_label()[: -1])
              self.extend_label(char)
      #@+node:ekr.20140813052702.18199: *3* fnc.do_tab
      def do_tab(self):
          '''Handle tab completion.'''
          old = self.get_label()
          common_prefix, tabList = self.compute_tab_list()
          self.show_tab_list(tabList)
          if len(tabList) == 1:
              common_prefix = tabList[0]
              self.set_label(common_prefix)
          elif len(common_prefix) > len(old):
              self.set_label(common_prefix)
      #@+node:ekr.20140813052702.18200: *3* fnc.get_file_name (entry)
      def get_file_name(self, event, callback, filterExt, prompt, tabName):
          '''Get a file name, supporting file completion.'''
          c, k = self.c, self.c.k
          tag = 'get-file-name'
          state = k.getState(tag)
          char = event.char if event else ''
          if state == 0:
              # Re-init all ivars.
              self.log = c.frame.log or g.NullObject()
              self.callback = callback
              self.filterExt = filterExt or ['.pyc', '.bin',]
              self.prompt = prompt
              self.tabName = tabName
              join = g.os_path_finalize_join
              finalize = g.os_path_finalize
              normslashes = g.os_path_normslashes
              # #467: Add setting for preferred directory.
              directory = c.config.getString('initial-chooser-directory')
              if directory:
                  directory = finalize(directory)
                  if not g.os_path_exists(directory):
                      g.es_print('@string initial-chooser-directory not found', 
                          normslashes(directory))
                      directory = None
              if not directory:
                  directory = finalize(os.curdir)
              # Init the label and state.
              tail = k.functionTail and k.functionTail.strip()
              label = join(directory, tail) if tail else directory + os.sep
              self.set_label(normslashes(label))
              k.setState(tag, 1, self.get_file_name)
              self.log.selectTab(self.tabName)
              junk, tabList = self.compute_tab_list()
              self.show_tab_list(tabList)
              c.minibufferWantsFocus()
          elif char == 'Escape':
              k.keyboardQuit()
          elif char in ('\n', 'Return'):
              self.log.deleteTab(self.tabName)
              path = self.get_label()
              k.keyboardQuit()
              if self.callback:
                  # pylint: disable=not-callable
                  self.callback(path)
              else:
                  g.trace('no callback')
          elif char in ('\t', 'Tab'):
              self.do_tab()
              c.minibufferWantsFocus()
          elif char in ('\b', 'BackSpace'):
              self.do_back_space()
              c.minibufferWantsFocus()
          elif k.isPlainKey(char):
              self.do_char(char)
          else:
              pass
      #@+node:ekr.20140813052702.18201: *3* fnc.extend/get/set_label
      def extend_label(self, s):
          '''Extend the label by s.'''
          self.c.k.extendLabel(s, select=False, protect=False)
  
      def get_label(self):
          '''Return the label, not including the prompt.'''
          return self.c.k.getLabel(ignorePrompt=True)
  
      def set_label(self, s):
          '''Set the label after the prompt to s. The prompt never changes.'''
          self.c.k.setLabel(self.prompt, protect=True)
          self.c.k.extendLabel(s or '', select=False, protect=False)
      #@+node:ekr.20140813052702.18202: *3* fnc.show_tab_list
      def show_tab_list(self, tabList):
          '''Show the tab list in the log tab.'''
          self.log.clearTab(self.tabName)
          s = g.os_path_finalize(os.curdir) + os.sep
          es = []
          for path in tabList:
              theDir, fileName = g.os_path_split(path)
              s = theDir if path.endswith(os.sep) else fileName
              s = fileName or g.os_path_basename(theDir) + os.sep
              es.append(s)
          g.es('', '\n'.join(es), tabName=self.tabName)
      #@-others
  #@+node:ekr.20140816165728.18940: ** class GetArg
  class GetArg:
      '''
      A class encapsulating all k.getArg logic.
  
      k.getArg maps to ga.get_arg, which gets arguments in the minibuffer.
  
      For details, see the docstring for ga.get_arg
      '''
      #@+others
      #@+node:ekr.20140818052417.18241: *3* ga.birth
      #@+node:ekr.20140816165728.18952: *4* ga.__init__
      def __init__(self, c, prompt='full-command: ', tabName='Completion'):
          '''Ctor for GetArg class.'''
          # Common ivars.
          self.c = c
          self.k = c.k
          assert c
          assert c.k
          self.log = c.frame.log or g.NullObject()
          self.tabName = tabName
          # State vars.
          self.after_get_arg_state = None, None, None
          self.arg_completion = True
          self.handler = None
          self.tabList = []
          # Tab cycling ivars...
          self.cycling_prefix = None
          self.cycling_index = -1
          self.cycling_tabList = []
          # The following are k globals.
              # k.arg.
              # k.argSelectedText
              # k.oneCharacterArg
      #@+node:ekr.20140817110228.18321: *3* ga.compute_tab_list
      # Called from k.doTabCompletion: with tabList = list(c.commandsDict.keys())
  
      def compute_tab_list(self, tabList, backspace=False, allow_empty_completion=False):
          '''Compute and show the available completions.'''
          # Support vim-mode commands.
          command = self.get_label()
          if self.is_command(command):
              tabList, common_prefix = g.itemsMatchingPrefixInList(command, tabList)
              return common_prefix, tabList
          #
          # For now, disallow further completions if something follows the command.
          command = self.get_command(command)
          return command, [command]
      #@+node:ekr.20140816165728.18965: *3* ga.do_back_space (entry)
      # Called from k.fullCommand: with defaultTabList = list(c.commandsDict.keys())
  
      def do_back_space(self, tabList, completion=True):
          '''Handle a backspace and update the completion list.'''
          k = self.k
          self.tabList = tabList[:] if tabList else []
          # Update the label.
          w = k.w
          i, j = w.getSelectionRange()
          ins = w.getInsertPoint()
          if ins > len(k.mb_prefix):
              # Step 1: actually delete the character.
              i, j = w.getSelectionRange()
              if i == j:
                  ins -= 1
                  w.delete(ins)
                  w.setSelectionRange(ins, ins, insert=ins)
              else:
                  ins = i
                  w.delete(i, j)
                  w.setSelectionRange(i, i, insert=ins)
          if w.getAllText().strip():
              junk, tabList = self.compute_tab_list(self.tabList)
              # Do *not* extend the label to the common prefix.
          else:
              tabList = []
          if completion:
              # #323.
              common_prefix, tabList = self.compute_tab_list(tabList)
              self.show_tab_list(tabList)
              self.reset_tab_cycling()
      #@+node:ekr.20140817110228.18323: *3* ga.do_tab (entry) & helpers
      # Used by ga.get_arg and k.fullCommand.
  
      def do_tab(self, tabList, completion=True):
          '''Handle tab completion when the user hits a tab.'''
          c = self.c
          if completion:
              tabList = self.tabList = tabList[:] if tabList else []
              common_prefix, tabList = self.compute_tab_list(tabList)
              if self.cycling_prefix and not self.cycling_prefix.startswith(common_prefix):
                  self.cycling_prefix = common_prefix
              #
              # No tab cycling for completed commands having
              # a 'tab_callback' attribute.
              if len(tabList) == 1 and self.do_tab_callback():
                  return
              # #323: *Always* call ga.do_tab_list.
              self.do_tab_cycling(common_prefix, tabList)
          c.minibufferWantsFocus()
      #@+node:ekr.20140818145250.18235: *4* ga.do_tab_callback
      def do_tab_callback(self):
          '''
          If the command-name handler has a tab_callback,
          call handler.tab_callback() and return True.
          '''
          c, k = self.c, self.k
          commandName, tail = k.getMinibufferCommandName()
          handler = c.commandsDict.get(commandName)
          if hasattr(handler, 'tab_callback'):
              self.reset_tab_cycling()
              k.functionTail = tail
                  # For k.getFileName.
              handler.tab_callback()
              return True
          return False
      #@+node:ekr.20140819050118.18317: *4* ga.do_tab_cycling
      def do_tab_cycling(self, common_prefix, tabList):
          '''Put the next (or first) completion in the minibuffer.'''
          s = self.get_label()
          if not common_prefix:
              # Leave the minibuffer as it is.
              self.show_tab_list(tabList)
          # #323.
          elif (
              self.cycling_prefix and
              s.startswith(self.cycling_prefix) and
              sorted(self.cycling_tabList) == sorted(tabList) # Bug fix: 2016/10/14
          ):
              n = self.cycling_index
              n = self.cycling_index = n + 1 if n + 1 < len(self.cycling_tabList) else 0
              self.set_label(self.cycling_tabList[n])
              self.show_tab_list(self.cycling_tabList)
          else:
              # Restart.
              self.show_tab_list(tabList)
              self.cycling_tabList = tabList[:]
              self.cycling_prefix = common_prefix
              self.set_label(common_prefix)
              if tabList and common_prefix == tabList[0]:
                  self.cycling_index = 0
              else:
                  self.cycling_index = -1
      #@+node:ekr.20140819050118.18318: *4* ga.reset_tab_cycling
      def reset_tab_cycling(self):
          '''Reset all tab cycling ivars.'''
          self.cycling_prefix = None
          self.cycling_index = -1
          self.cycling_tabList = []
      #@+node:ekr.20140816165728.18958: *3* ga.extend/get/set_label
      # Not useful because k.entendLabel doesn't handle selected text.
      if 0:
  
          def extend_label(self, s):
              '''Extend the label by s.'''
              self.c.k.extendLabel(s, select=False, protect=False)
  
      def get_label(self):
          '''Return the label, not including the prompt.'''
          return self.c.k.getLabel(ignorePrompt=True)
  
      def set_label(self, s):
          '''Set the label after the prompt to s. The prompt never changes.'''
          k = self.c.k
          # Using k.mb_prefix is simplest.  No ga.ivars need be inited.
          k.setLabel(k.mb_prefix, protect=True)
          k.extendLabel(s or '', select=False, protect=False)
      #@+node:ekr.20140816165728.18941: *3* ga.get_arg (entry) & helpers
      def get_arg(self, event,
          returnKind=None, returnState=None, handler=None,
          tabList=None, completion=True, oneCharacter=False,
          stroke=None, useMinibuffer=True
      ):
          #@+<< ga.get_arg docstring >>
          #@+node:ekr.20140822051549.18299: *4* << ga.get_arg docstring >>
          '''
          Accumulate an argument. Enter the given return state when done.
  
          Ctrl-G will abort this processing at any time.
  
          All commands needing user input call k.getArg, which just calls ga.get_arg.
  
          The arguments to ga.get_arg are as follows:
  
          event:              The event passed to the command.
  
          returnKind=None:    A string.
          returnState=None,   An int.
          handler=None,       A function.
  
              When the argument is complete, ga.do_end does::
  
                  if kind: k.setState(kind,n,handler)
  
          tabList=[]:         A list of possible completions.
  
          completion=True:    True if completions are enabled.
  
          oneCharacter=False: True if k.arg should be a single character.
  
          stroke=None:        The incoming key stroke.
  
          useMinibuffer=True: True: put focus in the minibuffer while accumulating arguments.
                              False allows sort-lines, for example, to show the selection range.
  
          '''
          #@-<< ga.get_arg docstring >>
          if tabList is None: tabList = []
          c, k = self.c, self.k
          state = k.getState('getArg')
          c.check_event(event)
          c.minibufferWantsFocusNow()
          char = event.char if event else ''
          if state > 0:
              k.setLossage(char, stroke)
          if state == 0:
              self.do_state_zero(completion, event, handler, oneCharacter,
                  returnKind, returnState, tabList, useMinibuffer)
              return
          if char == 'Escape':
              k.keyboardQuit()
          elif self.should_end(char, stroke):
              self.do_end(event, char, stroke)
          elif char in ('\t', 'Tab'):
              self.do_tab(self.tabList, self.arg_completion)
          elif char in ('\b', 'BackSpace'):
              self.do_back_space(self.tabList, self.arg_completion)
              c.minibufferWantsFocus()
          elif k.isFKey(stroke):
              # Ignore only F-keys. Ignoring all except plain keys would kill unicode searches.
              pass
          else:
              self.do_char(event, char)
      #@+node:ekr.20161019060054.1: *4* ga.cancel_after_state
      def cancel_after_state(self):
  
          self.after_get_arg_state = None
      #@+node:ekr.20140816165728.18955: *4* ga.do_char
      def do_char(self, event, char):
          '''Handle a non-special character.'''
          k = self.k
          k.updateLabel(event)
          # Any plain key resets tab cycling.
          self.reset_tab_cycling()
      #@+node:ekr.20140817110228.18316: *4* ga.do_end
      def do_end(self, event, char, stroke):
          '''A return or escape has been seen.'''
          k = self.k
          if char == '\t' and char in k.getArgEscapes:
              k.getArgEscapeFlag = True
          if stroke and stroke in k.getArgEscapes:
              k.getArgEscapeFlag = True
          # Get the value.
          gui_arg = getattr(g.app.gui, 'curses_gui_arg', None)
          if k.oneCharacterArg:
              k.arg = char
          else:
              # A hack to support the curses gui.
              k.arg = gui_arg or self.get_label()
          kind, n, handler = self.after_get_arg_state
          if kind:
              k.setState(kind, n, handler)
          self.log.deleteTab('Completion')
          self.reset_tab_cycling()
          if handler:
              # pylint: disable=not-callable
              handler(event)
      #@+node:ekr.20140817110228.18317: *4* ga.do_state_zero
      def do_state_zero(self, completion, event, handler, oneCharacter,
          returnKind, returnState, tabList, useMinibuffer
      ):
          '''Do state 0 processing.'''
          c, k = self.c, self.k
          #
          # Set the ga globals...
          k.getArgEscapeFlag = False
          self.after_get_arg_state = returnKind, returnState, handler
          self.arg_completion = completion
          self.cycling_prefix = None
          self.handler = handler
          self.tabList = tabList[:] if tabList else []
          #
          # Set the k globals...
          k.argSelectedText = c.frame.body.wrapper.getSelectedText()
          k.functionTail = None
          k.oneCharacterArg = oneCharacter
          #
          # Do *not* change the label here!
          # Enter the next state.
          c.widgetWantsFocus(c.frame.body.wrapper)
          k.setState('getArg', 1, k.getArg)
          # pylint: disable=consider-using-ternary
          k.afterArgWidget = event and event.widget or c.frame.body.wrapper
          if useMinibuffer: c.minibufferWantsFocus()
      #@+node:ekr.20140818103808.18234: *4* ga.should_end
      def should_end(self, char, stroke):
          '''Return True if ga.get_arg should return.'''
          k = self.k
          return (
              char in ('\n', 'Return',) or
              k.oneCharacterArg or
              stroke and stroke in k.getArgEscapes or
              char == '\t' and char in k.getArgEscapes
                  # The Find Easter Egg.
          )
      #@+node:ekr.20140818103808.18235: *4* ga.trace_state
      def trace_state(self, char, completion, handler, state, stroke):
          '''Trace the vars and ivars.'''
          k = self.c.k
          g.trace(
              'state', state, 'char', repr(char), 'stroke', repr(stroke),
              # 'isPlain',k.isPlainKey(stroke),
              '\n',
              'escapes', k.getArgEscapes,
              'completion', self.arg_completion,
              'handler', self.handler and self.handler.__name__ or 'None',
          )
      #@+node:ekr.20140818074502.18222: *3* ga.get_command
      def get_command(self, s):
          '''Return the command part of a minibuffer contents s.'''
          # #1121.
          if ' ' in s:
              return s[:s.find(' ')].strip()
          return s
      #@+node:ekr.20140818085719.18227: *3* ga.get_minibuffer_command_name
      def get_minibuffer_command_name(self):
          '''Return the command name in the minibuffer.'''
          s = self.get_label()
          command = self.get_command(s)
          tail = s[len(command):]
          return command, tail
      #@+node:ekr.20140818074502.18221: *3* ga.is_command
      def is_command(self, s):
          '''Return False if something, even a blank, follows a command.'''
          # #1121: only ascii space terminates a command.
          return ' ' not in s
      #@+node:ekr.20140816165728.18959: *3* ga.show_tab_list & helper
      def show_tab_list(self, tabList):
          '''Show the tab list in the log tab.'''
          k = self.k
          self.log.clearTab(self.tabName)
          d = k.computeInverseBindingDict()
          data, legend, n = [], False, 0
          for commandName in tabList:
              dataList = d.get(commandName, [])
              if dataList:
                  for z in dataList:
                      pane, key = z
                      s1a = '' if pane in ('all:', 'button:') else '%s ' % (pane)
                      s1b = k.prettyPrintKey(key)
                      s1 = s1a + s1b
                      s2 = self.command_source(commandName)
                      if s2 != ' ': legend = True
                      s3 = commandName
                      data.append((s1, s2, s3),)
                      n = max(n, len(s1))
              else:
                  # Bug fix: 2017/03/26
                  data.append(('',' ', commandName),)
          aList = ['%*s %s %s' % (-n, z1, z2, z3) for z1, z2, z3 in data]
          if legend:
              aList.extend([
                  '',
                  'legend:',
                  'G leoSettings.leo',
                  'M myLeoSettings.leo',
                  'L local .leo File',
              ])
          g.es('', '\n'.join(aList), tabName=self.tabName)
      #@+node:ekr.20150402034643.1: *4* ga.command_source
      def command_source(self, commandName):
          '''
          Return the source legend of an @button/@command node.
          'G' leoSettings.leo
          'M' myLeoSettings.leo
          'L' local .leo File
          ' ' not an @command or @button node
          '''
          c = self.c
          if commandName.startswith('@'):
              d = c.commandsDict
              func = d.get(commandName)
              if hasattr(func, 'source_c'):
                  c2 = func.source_c
                  fn2 = c2.shortFileName().lower()
                  if fn2.endswith('myleosettings.leo'):
                      return 'M'
                  if fn2.endswith('leosettings.leo'):
                      return 'G'
                  return 'L'
              return '?'
          return ' '
      #@-others
  #@+node:ekr.20061031131434.74: ** class KeyHandlerClass
  class KeyHandlerClass:
      '''
      A class to support emacs-style commands.
      c.k is an instance of this class.
      '''
      #@+others
      #@+node:ekr.20061031131434.75: *3*  k.Birth
      #@+node:ekr.20061031131434.76: *4* k.__init__& helpers
      def __init__(self, c):
          '''Create a key handler for c.'''
          self.c = c
          self.dispatchEvent = None
          self.fnc = None
              # A singleton defined in k.finishCreate.
          self.getArgInstance = None
              # A singleton defined in k.finishCreate.
          self.inited = False
              # Set at end of finishCreate.
          self.killedBindings = []
              # A list of commands whose bindings have been set to None in the local file.
          self.replace_meta_with_alt = False
              # True: (Mac only) swap Meta and Alt keys.
          self.w = None
              # Note: will be None for NullGui.
          # Generalize...
          self.x_hasNumeric = ['sort-lines', 'sort-fields']
          self.altX_prompt = 'full-command: '
          # Access to data types defined in leoKeys.py
          self.KeyStroke = g.KeyStroke
          # Define all ivars...
          self.defineExternallyVisibleIvars()
          self.defineInternalIvars()
          self.reloadSettings()
          self.defineSingleLineCommands()
          self.defineMultiLineCommands()
          self.autoCompleter = AutoCompleterClass(self)
          self.qcompleter = None # Set by AutoCompleter.start.
          self.setDefaultUnboundKeyAction()
          self.setDefaultEditingAction()
      #@+node:ekr.20061031131434.78: *5* k.defineExternallyVisibleIvars
      def defineExternallyVisibleIvars(self):
          self.abbrevOn = False
              # True: abbreviations are on.
          self.arg = ''
              # The value returned by k.getArg.
          self.argSelectedText = '' # The selected text in state 0.
          self.commandName = None # The name of the command being executed.
          self.funcReturn = None # For k.simulateCommand
          self.functionTail = None # For commands that take minibuffer arguments.
          # These are true globals
          self.getArgEscapes = []
          self.getArgEscapeFlag = False # A signal that the user escaped getArg in an unusual way.
          self.givenArgs = [] # New in Leo 4.4.8: arguments specified after the command name in k.simulateCommand.
          self.inputModeBindings = {}
          self.inputModeName = '' # The name of the input mode, or None.
          self.modePrompt = '' # The mode promopt.
          self.negativeArg = False
          self.newMinibufferWidget = None # Usually the minibuffer restores focus.  This overrides this default.
          # self.regx = g.bunch(iter=None,key=None)
          self.repeatCount = None
          self.state = g.bunch(kind=None, n=None, handler=None)
      #@+node:ekr.20061031131434.79: *5* k.defineInternalIvars
      def defineInternalIvars(self):
          '''Define internal ivars of the KeyHandlerClass class.'''
          self.abbreviationsDict = {}
              # Abbreviations created by @alias nodes.
          # Previously defined bindings...
          self.bindingsDict = {}
              # Keys are Tk key names, values are lists of BindingInfo objects.
          # Previously defined binding tags.
          self.bindtagsDict = {}
              # Keys are strings (the tag), values are 'True'
          self.commandHistory = []
          self.commandIndex = 0
              # List/stack of previously executed commands.
              # Up arrow will select commandHistory[commandIndex]
          self.masterBindingsDict = {}
              # Keys are scope names: 'all','text',etc. or mode names.
              # Values are dicts: keys are strokes, values are BindingInfo objects.
          self.masterGuiBindingsDict = {}
              # Keys are strokes; value is True;
          # Special bindings for k.fullCommand...
          self.mb_copyKey = None
          self.mb_pasteKey = None
          self.mb_cutKey = None
          # Keys whose bindings are computed by initSpecialIvars...
          self.abortAllModesKey = None
          self.autoCompleteForceKey = None
          self.demoNextKey = None # New support for the demo.py plugin.
          self.demoPrevKey = None # New support for the demo.py plugin.
          self.fullCommandKey = None
          self.universalArgKey = None
          # Used by k.masterKeyHandler...
          self.stroke = None
          self.mb_event = None
          self.mb_history = []
          self.mb_help = False
          self.mb_helpHandler = None
          # Important: these are defined in k.defineExternallyVisibleIvars...
              # self.getArgEscapes = []
              # self.getArgEscapeFlag
          # For onIdleTime...
          self.idleCount = 0
          # For modes...
          self.modeBindingsDict = {}
          self.modeWidget = None
          self.silentMode = False
      #@+node:ekr.20080509064108.7: *5* k.defineMultiLineCommands
      def defineMultiLineCommands(self):
          k = self
          k.multiLineCommandList = [
              # EditCommandsClass
              'add-space-to-lines',
              'add-tab-to-lines',
              'back-page',
              'back-page-extend-selection',
              'back-paragraph',
              'back-paragraph-extend-selection',
              'back-sentence',
              'back-sentence-extend-selection',
              'backward-kill-paragraph',
              'beginning-of-buffer',
              'beginning-of-buffer-extend-selection',
              'center-line',
              'center-region',
              'clean-all-lines',
              'clean-lines',
              'downcase-region',
              'end-of-buffer',
              'end-of-buffer-extend-selection',
              'extend-to-paragraph',
              'extend-to-sentence',
              'fill-paragraph',
              'fill-region',
              'fill-region-as-paragraph',
              'flush-lines',
              'forward-page',
              'forward-page-extend-selection',
              'forward-paragraph',
              'forward-paragraph-extend-selection',
              'forward-sentence',
              'forward-sentence-extend-selection',
              'indent-relative',
              'indent-rigidly',
              'indent-to-comment-column',
              'move-lines-down',
              'move-lines-up',
              'next-line',
              'next-line-extend-selection',
              'previous-line',
              'previous-line-extend-selection',
              'remove-blank-lines',
              'remove-space-from-lines',
              'remove-tab-from-lines',
              'reverse-region',
              'reverse-sort-lines',
              'reverse-sort-lines-ignoring-case',
              'scroll-down-half-page',
              'scroll-down-line',
              'scroll-down-page',
              'scroll-up-half-page',
              'scroll-up-line',
              'scroll-up-page',
              'simulate-begin-drag',
              'simulate-end-drag',
              'sort-columns',
              'sort-fields',
              'sort-lines',
              'sort-lines-ignoring-case',
              'split-line',
              'tabify',
              'transpose-lines',
              'untabify',
              'upcase-region',
              # KeyHandlerCommandsClass
              'repeat-complex-command',
              # KillBufferCommandsClass
              'backward-kill-sentence',
              'kill-sentence',
              'kill-region',
              'kill-region-save',
              # QueryReplaceCommandsClass
              'query-replace',
              'query-replace-regex',
              # RectangleCommandsClass
              'clear-rectangle',
              'close-rectangle',
              'delete-rectangle',
              'kill-rectangle',
              'open-rectangle',
              'string-rectangle',
              'yank-rectangle',
              # SearchCommandsClass
              'change',
              'change-then-find',
              'find-next',
              'find-prev',
          ]
      #@+node:ekr.20080509064108.6: *5* k.defineSingleLineCommands
      def defineSingleLineCommands(self):
          k = self
          # These commands can be executed in the minibuffer.
          k.singleLineCommandList = [
              # EditCommandsClass
              'back-to-indentation',
              'back-to-home', # 2010/02/01
              'back-char',
              'back-char-extend-selection',
              'back-word',
              'back-word-extend-selection',
              'backward-delete-char',
              'backward-find-character',
              'backward-find-character-extend-selection',
              'beginning-of-line',
              'beginning-of-line-extend-selection',
              'capitalize-word',
              'delete-char',
              'delete-indentation',
              'delete-spaces',
              'downcase-word',
              'end-of-line',
              'end-of-line-extend-selection',
              'escape',
              'exchange-point-mark',
              'extend-to-line',
              'extend-to-word',
              'find-character',
              'find-character-extend-selection',
              'find-word',
              'find-word-in-line',
              'forward-char',
              'forward-char-extend-selection',
              'forward-end-word',
              'forward-end-word-extend-selection',
              'forward-word',
              'forward-word-extend-selection',
              'insert-newline',
              'insert-parentheses',
              'move-past-close',
              'move-past-close-extend-selection',
              'newline-and-indent',
              'select-all',
              'transpose-chars',
              'transpose-words',
              'upcase-word',
              # KeyHandlerCommandsClass
              # 'auto-complete',
                  # 'negative-argument',
                  # 'number-command',
                  # 'number-command-0',
                  # 'number-command-1',
                  # 'number-command-2',
                  # 'number-command-3',
                  # 'number-command-4',
                  # 'number-command-5',
                  # 'number-command-6',
                  # 'number-command-7',
                  # 'number-command-8',
                  # 'universal-argument',
              # KillBufferCommandsClass
              'backward-kill-word',
              'kill-line',
              'kill-word',
              'kill-ws',
              'yank',
              'yank-pop',
              'zap-to-character',
              # leoCommands
              'cut-text',
              'copy-text',
              'paste-text',
              # MacroCommandsClass
              'call-last-kbd-macro',
              # search commands
              # 'replace-string', # A special case so Shift-Ctrl-r will work after Ctrl-f.
              'set-find-everywhere', # 2011/06/07
              'set-find-node-only', # 2011/06/07
              'set-find-suboutline-only', # 2011/06/07
              'toggle-find-collapses_nodes',
              'toggle-find-ignore-case-option',
              'toggle-find-in-body-option',
              'toggle-find-in-headline-option',
              'toggle-find-mark-changes-option',
              'toggle-find-mark-finds-option',
              'toggle-find-regex-option',
              'toggle-find-reverse-option',
              'toggle-find-word-option',
              'toggle-find-wrap-around-option',
          ]
      #@+node:ekr.20150509035028.1: *4* k.cmd (decorator)
      def cmd(name):
          '''Command decorator for the leoKeys class.'''
          # pylint: disable=no-self-argument
          return g.new_cmd_decorator(name, ['c', 'k',])
      #@+node:ekr.20061031131434.80: *4* k.finishCreate & helpers
      def finishCreate(self):
          '''
          Complete the construction of the keyHandler class.
          c.commandsDict has been created when this is called.
          '''
          c, k = self.c, self
          k.w = c.frame.miniBufferWidget
              # Will be None for NullGui.
          k.fnc = FileNameChooser(c)
              # A singleton. Defined here so that c.k will exist.
          k.getArgInstance = GetArg(c)
              # a singleton. Defined here so that c.k will exist.
          k.makeAllBindings()
              # Important: This must be called this now,
              # even though LM.laod calls g.app.makeAllBindings later.
          k.initCommandHistory()
          k.inited = True
          k.setDefaultInputState()
          k.resetLabel()
      #@+node:ekr.20061101071425: *4* k.oops
      def oops(self):
  
          g.trace('Should be defined in subclass:', g.callers(4))
      #@+node:ekr.20120217070122.10479: *4* k.reloadSettings
      def reloadSettings(self):
          # Part 1: These were in the ctor.
          c = self.c
          getBool = c.config.getBool
          getColor = c.config.getColor
          self.enable_autocompleter = getBool('enable-autocompleter-initially')
          self.enable_calltips = getBool('enable-calltips-initially')
          self.ignore_unbound_non_ascii_keys = getBool('ignore-unbound-non-ascii-keys')
          self.minibuffer_background_color = getColor('minibuffer-background-color') or 'lightblue'
          self.minibuffer_foreground_color = getColor('minibuffer-foreground-color') or 'black'
          self.minibuffer_warning_color = getColor('minibuffer-warning-color') or 'lightgrey'
          self.minibuffer_error_color = getColor('minibuffer-error-color') or 'red'
          self.replace_meta_with_alt = getBool('replace-meta-with-alt')
          self.warn_about_redefined_shortcuts = getBool('warn-about-redefined-shortcuts')
          # Has to be disabled (default) for AltGr support on Windows
          self.enable_alt_ctrl_bindings = c.config.getBool('enable-alt-ctrl-bindings')
          # Part 2: These were in finishCreate.
          # Set mode colors used by k.setInputState.
          bg = c.config.getColor('body-text-background-color') or 'white'
          fg = c.config.getColor('body-text-foreground-color') or 'black'
          self.command_mode_bg_color = getColor('command-mode-bg-color') or bg
          self.command_mode_fg_color = getColor('command-mode-fg-color') or fg
          self.insert_mode_bg_color = getColor('insert-mode-bg-color') or bg
          self.insert_mode_fg_color = getColor('insert-mode-fg-color') or fg
          self.overwrite_mode_bg_color = getColor('overwrite-mode-bg-color') or bg
          self.overwrite_mode_fg_color = getColor('overwrite-mode-fg-color') or fg
          self.unselected_body_bg_color = getColor('unselected-body-bg-color') or bg
          self.unselected_body_fg_color = getColor('unselected-body-fg-color') or bg
      #@+node:ekr.20110209093958.15413: *4* k.setDefaultEditingKeyAction (New)
      def setDefaultEditingAction(self):
          k = self; c = k.c
          action = c.config.getString('default-editing-state') or 'insert'
          action.lower()
          if action not in ('command', 'insert', 'overwrite'):
              g.trace('ignoring default_editing_state: %s' % (action))
              action = 'insert'
          self.defaultEditingAction = action
      #@+node:ekr.20061031131434.82: *4* k.setDefaultUnboundKeyAction
      def setDefaultUnboundKeyAction(self, allowCommandState=True):
          k = self; c = k.c
          defaultAction = c.config.getString('top-level-unbound-key-action') or 'insert'
          defaultAction.lower()
          if defaultAction == 'command' and not allowCommandState:
              self.unboundKeyAction = 'insert'
          elif defaultAction in ('command', 'insert', 'overwrite'):
              self.unboundKeyAction = defaultAction
          else:
              g.trace('ignoring top_level_unbound_key_action setting: %s' % (defaultAction))
              self.unboundKeyAction = 'insert'
          self.defaultUnboundKeyAction = self.unboundKeyAction
          k.setInputState(self.defaultUnboundKeyAction)
      #@+node:ekr.20061031131434.88: *3* k.Binding
      #@+node:ekr.20061031131434.89: *4* k.bindKey & helpers
      def bindKey(self, pane, shortcut, callback, commandName, modeFlag=False, tag=None):
          '''
          Bind the indicated shortcut (a Tk keystroke) to the callback.
  
          No actual gui bindings are made: only entries in k.masterBindingsDict
          and k.bindingsDict.
  
          tag gives the source of the binding.
          
          Return True if the binding was made successfully.
          '''
          k = self
          if not shortcut:
              # Don't use this method to undo bindings.
              return False
          if not k.check_bind_key(commandName, pane, shortcut):
              return False
          aList = k.bindingsDict.get(shortcut, [])
          try:
              if not shortcut:
                  stroke = None
              elif g.isStroke(shortcut):
                  stroke = shortcut
                  assert stroke.s, stroke
              else:
                  assert shortcut, g.callers()
                  stroke = g.KeyStroke(binding=shortcut)
              bi = g.BindingInfo(
                  kind=tag,
                  pane=pane,
                  func=callback,
                  commandName=commandName,
                  stroke=stroke)
              if shortcut:
                  k.bindKeyToDict(pane, shortcut, bi)
                      # Updates k.masterBindingsDict
              if shortcut and not modeFlag:
                  aList = k.remove_conflicting_definitions(
                      aList, commandName, pane, shortcut)
                  # 2013/03/02: a real bug fix.
              aList.append(bi)
              if shortcut:
                  assert stroke
                  k.bindingsDict[stroke] = aList
              return True
          except Exception: # Could be a user error.
              if g.unitTesting or not g.app.menuWarningsGiven:
                  g.es_print('exception binding', shortcut, 'to', commandName)
                  g.es_print_exception()
                  g.app.menuWarningsGiven = True
              return False
  
      bindShortcut = bindKey # For compatibility
      #@+node:ekr.20120130074511.10228: *5* k.check_bind_key
      def check_bind_key(self, commandName, pane, stroke):
          '''
          Return True if the binding of stroke to commandName for the given
          pane can be made.
          '''
          # k = self
          assert g.isStroke(stroke)
          # Give warning and return if we try to bind to Enter or Leave.
          for s in ('enter', 'leave'):
              if stroke.lower().find(s) > -1:
                  g.warning('ignoring invalid key binding:', '%s = %s' % (
                      commandName, stroke))
                  return False
          if pane.endswith('-mode'):
              g.trace('oops: ignoring mode binding', stroke, commandName, g.callers())
              return False
          return True
      #@+node:ekr.20120130074511.10227: *5* k.kill_one_shortcut
      def kill_one_shortcut(self, stroke):
          '''
          Update the *configuration* dicts so that c.config.getShortcut(name)
          will return None for all names *presently* bound to the stroke.
          '''
          k = self; c = k.c
          lm = g.app.loadManager
          if 0:
              # This does not fix 327: Create a way to unbind bindings
              assert stroke in (None, 'None', 'none') or g.isStroke(stroke), repr(stroke)
          else:
              # A crucial shortcut: inverting and uninverting dictionaries is slow.
              # Important: the comparison is valid regardless of the type of stroke.
              if stroke in (None, 'None', 'none'):
                  return
              assert g.isStroke(stroke), stroke
          d = c.config.shortcutsDict
          if d is None:
              d = g.TypedDictOfLists(
                  name='empty shortcuts dict',
                  keyType=type('commandName'),
                  valType=g.BindingInfo)
          inv_d = lm.invert(d)
          inv_d[stroke] = []
          c.config.shortcutsDict = lm.uninvert(inv_d)
      #@+node:ekr.20061031131434.92: *5* k.remove_conflicting_definitions
      def remove_conflicting_definitions(self, aList, commandName, pane, shortcut):
  
          k = self
          result = []
          for bi in aList:
              if pane in ('button', 'all', bi.pane):
                  k.kill_one_shortcut(shortcut)
              else:
                  result.append(bi)
          return result
      #@+node:ekr.20061031131434.93: *5* k.bindKeyToDict
      def bindKeyToDict(self, pane, stroke, bi):
          '''Update k.masterBindingsDict for the stroke.'''
          # New in Leo 4.4.1: Allow redefintions.
          # Called from makeBindingsFromCommandsDict.
          k = self
          assert g.isStroke(stroke), stroke
          d = k.masterBindingsDict.get(pane, {})
          d[stroke] = bi
          k.masterBindingsDict[pane] = d
      #@+node:ekr.20061031131434.94: *5* k.bindOpenWith
      def bindOpenWith(self, d):
          '''Register an open-with command.'''
          k = self; c = k.c
          shortcut = d.get('shortcut') or ''
          name = d.get('name')
          # The first parameter must be event, and it must default to None.
  
          def openWithCallback(event=None, c=c, d=d):
              return c.openWith(d=d)
  
          # Use k.registerCommand to set the shortcuts in the various binding dicts.
          commandName = 'open-with-%s' % name.lower()
          k.registerCommand(
              allowBinding=True,
              commandName=commandName,
              func=openWithCallback,
              pane='all',
              shortcut=shortcut,
          )
      #@+node:ekr.20061031131434.95: *4* k.checkBindings
      def checkBindings(self):
          '''Print warnings if commands do not have any @shortcut entry.
          The entry may be `None`, of course.'''
          k = self; c = k.c
          if not c.config.getBool('warn-about-missing-settings'): return
          for name in sorted(c.commandsDict):
              abbrev = k.abbreviationsDict.get(name)
              key = c.frame.menu.canonicalizeMenuName(abbrev or name)
              key = key.replace('&', '')
              if not c.config.exists(key, 'shortcut'):
                  if abbrev:
                      g.trace('No shortcut for abbrev %s -> %s = %s' % (
                          name, abbrev, key))
                  else:
                      g.trace('No shortcut for %s = %s' % (name, key))
      #@+node:ekr.20061031131434.97: *4* k.completeAllBindings
      def completeAllBindings(self, w=None):
          '''New in 4.4b3: make an actual binding in *all* the standard places.
  
          The event will go to k.masterKeyHandler as always, so nothing really changes.
          except that k.masterKeyHandler will know the proper stroke.'''
          k = self
          for stroke in k.bindingsDict:
              assert g.isStroke(stroke), repr(stroke)
              k.makeMasterGuiBinding(stroke, w=w)
      #@+node:ekr.20061031131434.96: *4* k.completeAllBindingsForWidget
      def completeAllBindingsForWidget(self, w):
          '''Make all a master gui binding for widget w.'''
          k = self
          for stroke in k.bindingsDict:
              assert g.isStroke(stroke), repr(stroke)
              k.makeMasterGuiBinding(stroke, w=w)
      #@+node:ekr.20070218130238: *4* k.dumpMasterBindingsDict
      def dumpMasterBindingsDict(self):
          '''Dump k.masterBindingsDict.'''
          k = self; d = k.masterBindingsDict
          g.pr('\nk.masterBindingsDict...\n')
          for key in sorted(d):
              g.pr(key, '-' * 40)
              d2 = d.get(key)
              for key2 in sorted(d2):
                  bi = d2.get(key2)
                  g.pr('%20s %s' % (key2, bi.commandName))
      #@+node:ekr.20061031131434.99: *4* k.initAbbrev & helper
      def initAbbrev(self):
          k = self; c = k.c; d = c.config.getAbbrevDict()
          if d:
              for key in d:
                  commandName = d.get(key)
                  if commandName.startswith('press-') and commandName.endswith('-button'):
                      pass # Must be done later in k.registerCommand.
                  else:
                      self.initOneAbbrev(commandName, key)
      #@+node:ekr.20130924035029.12741: *5* k.initOneAbbrev
      def initOneAbbrev(self, commandName, key):
          '''Enter key as an abbreviation for commandName in c.commandsDict.'''
          c = self.c
          if c.commandsDict.get(key):
              g.trace('ignoring duplicate abbrev: %s', key)
          else:
              func = c.commandsDict.get(commandName)
              if func:
                  c.commandsDict[key] = func
              else:
                  g.warning('bad abbrev:', key, 'unknown command name:', commandName)
      #@+node:ekr.20061031131434.101: *4* k.initSpecialIvars
      def initSpecialIvars(self):
          '''Set ivars for special keystrokes from previously-existing bindings.'''
          c, k = self.c, self
          warn = c.config.getBool('warn-about-missing-settings')
          for ivar, commandName in (
              ('fullCommandKey', 'full-command'),
              ('abortAllModesKey', 'keyboard-quit'),
              ('universalArgKey', 'universal-argument'),
              ('autoCompleteForceKey', 'auto-complete-force'),
              ('demoNextKey', 'demo-next'),
              ('demoPrevKey', 'demo-prev'),
          ):
              junk, aList = c.config.getShortcut(commandName)
              aList, found = aList or [], False
              for pane in ('text', 'all'):
                  for bi in aList:
                      if bi.pane == pane:
                          setattr(k, ivar, bi.stroke)
                          found = True; break
              if not found and warn:
                  g.trace('no setting for %s' % commandName)
      #@+node:ekr.20061031131434.98: *4* k.makeAllBindings
      def makeAllBindings(self):
          '''Make all key bindings in all of Leo's panes.'''
          k = self
          k.bindingsDict = {}
          k.addModeCommands()
          k.makeBindingsFromCommandsDict()
          k.initSpecialIvars()
          k.initAbbrev()
          k.completeAllBindings()
          k.checkBindings()
      #@+node:ekr.20061031131434.102: *4* k.makeBindingsFromCommandsDict
      def makeBindingsFromCommandsDict(self):
          '''Add bindings for all entries in c.commandsDict.'''
          c, k = self.c, self
          d = c.commandsDict
          #
          # Step 1: Create d2.
          # Keys are strokes. Values are lists of bi with bi.stroke == stroke.
          d2 = g.TypedDictOfLists(
              name='makeBindingsFromCommandsDict helper dict',
              keyType=g.KeyStroke,
              valType=g.BindingInfo)
          for commandName in sorted(d):
              command = d.get(commandName)
              key, aList = c.config.getShortcut(commandName)
              for bi in aList:
                  # Important: bi.stroke is already canonicalized.
                  stroke = bi.stroke
                  bi.commandName = commandName
                  if stroke:
                      assert g.isStroke(stroke)
                      d2.add(stroke, bi)
          #
          # Step 2: make the bindings.
          for stroke in sorted(d2.keys()):
              aList2 = d2.get(stroke)
              for bi in aList2:
                  commandName = bi.commandName
                  command = c.commandsDict.get(commandName)
                  tag = bi.kind
                  pane = bi.pane
                  if stroke and not pane.endswith('-mode'):
                      k.bindKey(pane, stroke, command, commandName, tag=tag)
      #@+node:ekr.20061031131434.103: *4* k.makeMasterGuiBinding
      def makeMasterGuiBinding(self, stroke, w=None, trace=False):
          '''Make a master gui binding for stroke in pane w, or in all the standard widgets.'''
          k = self; c = k.c; f = c.frame
          if w:
              widgets = [w]
          else:
              # New in Leo 4.5: we *must* make the binding in the binding widget.
              bindingWidget = f.tree and hasattr(f.tree, 'bindingWidget') and f.tree.bindingWidget or None
              wrapper = f.body and hasattr(f.body, 'wrapper') and f.body.wrapper or None
              canvas = f.tree and hasattr(f.tree, 'canvas') and f.tree.canvas or None
              widgets = (c.miniBufferWidget, wrapper, canvas, bindingWidget)
          for w in widgets:
              if not w: continue
              # Make the binding only if no binding for the stroke exists in the widget.
              aList = k.masterGuiBindingsDict.get(stroke, [])
              if w not in aList:
                  aList.append(w)
                  k.masterGuiBindingsDict[stroke] = aList
      #@+node:ekr.20150402111403.1: *3* k.Command history
      #@+node:ekr.20150402111413.1: *4* k.addToCommandHistory
      def addToCommandHistory(self, commandName):
          '''Add a name to the command history.'''
          k = self
          h = k.commandHistory
          if commandName in h:
              h.remove(commandName)
          h.append(commandName)
          k.commandIndex = None
  
      #@+node:ekr.20150402165918.1: *4* k.commandHistoryDown
      def commandHistoryFwd(self):
          '''
          Move down the Command History - fall off the bottom (return empty string)
          if necessary
          '''
          k = self
          h, i = k.commandHistory, k.commandIndex
          if h:
              commandName = ''
              if i == len(h) -1:
                  # fall off the bottom
                  i = None
              elif i is not None:
                  # move to next down in list
                  i += 1
                  commandName = h[i]
              k.commandIndex = i
              k.setLabel(k.mb_prefix + commandName)
      #@+node:ekr.20150402171131.1: *4* k.commandHistoryUp
      def commandHistoryBackwd(self):
          '''
          Return the previous entry in the Command History - stay at the top
          if we are there
          '''
          k = self
          h, i = k.commandHistory, k.commandIndex
          if h:
              if i is None:
                  # first time in - set to last entry
                  i = len(h) -1
              elif i > 0:
                  i -= 1
              commandName = h[i]
              k.commandIndex = i
              k.setLabel(k.mb_prefix + commandName)
      #@+node:ekr.20150425143043.1: *4* k.initCommandHistory
      def initCommandHistory(self):
          '''Init command history from @data command-history nodes.'''
          k, c = self, self.c
          aList = c.config.getData('history-list') or []
          for command in reversed(aList):
              k.addToCommandHistory(command)
  
      def resetCommandHistory(self):
          ''' reset the command history index to indicate that
              we are pointing 'past' the last entry
          '''
          self.commandIndex = None
          #
      #@+node:ekr.20150402111935.1: *4* k.sortCommandHistory
      def sortCommandHistory(self):
          '''Sort the command history.'''
          k = self
          k.commandHistory.sort()
          k.commandIndex = None
      #@+node:ekr.20061031131434.104: *3* k.Dispatching
      #@+node:ekr.20061031131434.111: *4* k.fullCommand (alt-x) & helper
      @cmd('full-command')
      def fullCommand(self, event, specialStroke=None, specialFunc=None, help=False, helpHandler=None):
          '''Handle 'full-command' (alt-x) mode.'''
          try:
              k = self; c = k.c
              state = k.getState('full-command')
              helpPrompt = 'Help for command: '
              c.check_event(event)
              ch = char = event.char if event else ''
              stroke = event.stroke if event else ''
              if state > 0:
                  k.setLossage(char, stroke)
              if state == 0:
                  k.mb_event = event # Save the full event for later.
                  k.setState('full-command', 1, handler=k.fullCommand)
                  prompt = helpPrompt if help else k.altX_prompt
                  k.setLabelBlue(prompt)
                  k.mb_help = help
                  k.mb_helpHandler = helpHandler
                  c.minibufferWantsFocus()
              elif char == 'Ins' or k.isFKey(char):
                  pass
              elif char == 'Escape':
                  k.keyboardQuit()
              elif char == 'Down':
                  k.commandHistoryFwd()
              elif char == 'Up':
                  k.commandHistoryBackwd()
              elif char in ('\n', 'Return'):
                  # Fix bug 157: save and restore the selection.
                  w = k.mb_event and k.mb_event.w
                  if w and hasattr(w, 'hasSelection') and w.hasSelection():
                      sel1, sel2 = w.getSelectionRange()
                      ins = w.getInsertPoint()
                      c.frame.log.deleteTab('Completion')
                      w.setSelectionRange(sel1, sel2, insert=ins)
                  else:
                      c.frame.log.deleteTab('Completion')
                          # 2016/04/27
                  if k.mb_help:
                      s = k.getLabel()
                      commandName = s[len(helpPrompt):].strip()
                      k.clearState()
                      k.resetLabel()
                      if k.mb_helpHandler: k.mb_helpHandler(commandName)
                  else:
                      s = k.getLabel(ignorePrompt=True)
                      commandName = s.strip()
                      ok = k.callAltXFunction(k.mb_event)
                      if ok:
                          k.addToCommandHistory(commandName)
              elif char in ('\t', 'Tab'):
                  k.doTabCompletion(list(c.commandsDict.keys()))
                  c.minibufferWantsFocus()
              elif char in ('\b', 'BackSpace'):
                  k.doBackSpace(list(c.commandsDict.keys()))
                  c.minibufferWantsFocus()
              elif k.ignore_unbound_non_ascii_keys and len(ch) > 1:
                  if specialStroke:
                      g.trace(specialStroke)
                      specialFunc()
                  c.minibufferWantsFocus()
              else:
                  # Clear the list, any other character besides tab indicates that a new prefix is in effect.
                  k.mb_tabList = []
                  k.updateLabel(event)
                  k.mb_tabListPrefix = k.getLabel()
                  c.minibufferWantsFocus()
          except Exception:
              g.es_exception()
              self.keyboardQuit()
      #@+node:ekr.20061031131434.112: *5* k.callAltXFunction
      def callAltXFunction(self, event):
          '''Call the function whose name is in the minibuffer.'''
          c, k = self.c, self
          k.mb_tabList = []
          commandName, tail = k.getMinibufferCommandName()
          k.functionTail = tail
          if commandName and commandName.isdigit():
              # The line number Easter Egg.
              def func(event=None):
                  c.gotoCommands.find_file_line(n=int(commandName))
          else:
              func = c.commandsDict.get(commandName)
          k.newMinibufferWidget = None
          if func:
              # These must be done *after* getting the command.
              k.clearState()
              k.resetLabel()
              if commandName != 'repeat-complex-command':
                  k.mb_history.insert(0, commandName)
              w = event and event.widget
              if hasattr(w, 'permanent') and not w.permanent:
                  # In a headline that is being edited.
                  # g.es('Can not execute commands from headlines')
                  c.endEditing()
                  c.bodyWantsFocusNow()
                  # Change the event widget so we don't refer to the to-be-deleted headline widget.
                  event.w = event.widget = c.frame.body.wrapper.widget
                  c.executeAnyCommand(func, event)
              else:
                  c.widgetWantsFocusNow(event and event.widget)
                      # Important, so cut-text works, e.g.
                  c.executeAnyCommand(func, event)
              k.endCommand(commandName)
              return True
          #
          # Show possible completions if the command does not exist.
          if 1: # Useful.
              k.doTabCompletion(list(c.commandsDict.keys()))
          else: # Annoying.
              k.keyboardQuit()
              k.setStatusLabel('Command does not exist: %s' % commandName)
              c.bodyWantsFocus()
          return False
      #@+node:ekr.20061031131434.113: *4* k.endCommand
      def endCommand(self, commandName):
          '''Make sure Leo updates the widget following a command.
  
          Never changes the minibuffer label: individual commands must do that.
          '''
          k = self; c = k.c
          # The command may have closed the window.
          if g.app.quitting or not c.exists: return
          # Set the best possible undoType: prefer explicit commandName to k.commandName.
          commandName = commandName or k.commandName or ''
          k.commandName = k.commandName or commandName or ''
          if commandName:
              if not k.inState():
                  k.commandName = None
              if 0: # Do *not* call this by default.  It interferes with undo.
                  c.frame.body.onBodyChanged(undoType='Typing')
              if k.newMinibufferWidget:
                  c.widgetWantsFocusNow(k.newMinibufferWidget)
                  # g.pr('endCommand', g.app.gui.widget_name(k.newMinibufferWidget),g.callers())
                  k.newMinibufferWidget = None
      #@+node:ekr.20061031131434.114: *3* k.Externally visible commands
      #@+node:ekr.20070613133500: *4* k.menuCommandKey
      def menuCommandKey(self, event=None):
          # This method must exist, but it never gets called.
          pass
      #@+node:ekr.20061031131434.119: *4* k.printBindings & helper
      @cmd('show-bindings')
      def printBindings(self, event=None):
          '''Print all the bindings presently in effect.'''
          k = self; c = k.c
          d = k.bindingsDict; tabName = 'Bindings'
          c.frame.log.clearTab(tabName)
          legend = '''\
      legend:
      [ ] leoSettings.leo
      [D] default binding
      [F] loaded .leo File
      [M] myLeoSettings.leo
      [@] @mode, @button, @command
  
      '''
          if not d: return g.es('no bindings')
          legend = g.adjustTripleString(legend, c.tab_width)
          data = []
          for stroke in sorted(d):
              assert g.isStroke(stroke), stroke
              aList = d.get(stroke, [])
              for bi in aList:
                  s1 = '' if bi.pane == 'all' else bi.pane
                  s2 = k.prettyPrintKey(stroke)
                  s3 = bi.commandName
                  s4 = bi.kind or '<no hash>'
                  data.append((s1, s2, s3, s4),)
          # Print keys by type:
          result = []
          result.append('\n' + legend)
          for prefix in (
              'Alt+Ctrl+Shift', 'Alt+Ctrl', 'Alt+Shift', 'Alt', # 'Alt+Key': done by Alt.
              'Ctrl+Meta+Shift', 'Ctrl+Meta', 'Ctrl+Shift', 'Ctrl', # Ctrl+Key: done by Ctrl.
              'Meta+Key', 'Meta+Shift', 'Meta',
              'Shift',
              # Careful: longer prefixes must come before shorter prefixes.
          ):
              data2 = []
              for item in data:
                  s1, s2, s3, s4 = item
                  if s2.startswith(prefix):
                      data2.append(item)
              result.append('***** %s...\n' % prefix)
              self.printBindingsHelper(result, data2, prefix=prefix)
              # Remove all the items in data2 from data.
              # This must be done outside the iterator on data.
              for item in data2:
                  data.remove(item)
          # Print all plain bindings.
          result.append('***** Plain Keys...\n')
          self.printBindingsHelper(result, data, prefix=None)
          if not g.unitTesting:
              g.es_print('', ''.join(result), tabName=tabName)
          k.showStateAndMode()
          return result # for unit test.
      #@+node:ekr.20061031131434.120: *5* printBindingsHelper
      def printBindingsHelper(self, result, data, prefix):
          lm = g.app.loadManager
          data.sort(key=lambda x: x[1])
          data2, n = [], 0
          for pane, key, commandName, kind in data:
              key = key.replace('+Key', '')
              letter = lm.computeBindingLetter(kind)
              pane = '%4s: ' % (pane if pane else 'all')
              left = pane + key # pane and shortcut fields
              n = max(n, len(left))
              data2.append((letter, left, commandName),)
          for z in data2:
              letter, left, commandName = z
              result.append('%s %*s %s\n' % (letter, -n, left, commandName))
          if data:
              result.append('\n')
      #@+node:ekr.20120520174745.9867: *4* k.printButtons
      @cmd('show-buttons')
      def printButtons(self, event=None):
          '''Print all @button and @command commands, their bindings and their source.'''
          k = self; c = k.c
          tabName = '@buttons && @commands'
          c.frame.log.clearTab(tabName)
  
          def put(s):
              g.es('', s, tabName=tabName)
  
          data = []
          for aList in [c.config.getButtons(), c.config.getCommands()]:
              for z in aList:
                  p, script = z
                  c = p.v.context
                  tag = 'M' if c.shortFileName().endswith('myLeoSettings.leo') else 'G'
                  data.append((p.h, tag),)
          for aList in [g.app.config.atLocalButtonsList, g.app.config.atLocalCommandsList]:
              for p in aList:
                  data.append((p.h, 'L'),)
          result = ['%s %s' % (z[1], z[0]) for z in sorted(data)]
          result.extend([
              '',
              'legend:',
              'G leoSettings.leo',
              'L local .leo File',
              'M myLeoSettings.leo',
          ])
          put('\n'.join(result))
      #@+node:ekr.20061031131434.121: *4* k.printCommands
      @cmd('show-commands')
      def printCommands(self, event=None):
          '''Print all the known commands and their bindings, if any.'''
          k = self; c = k.c; tabName = 'Commands'
          c.frame.log.clearTab(tabName)
          inverseBindingDict = k.computeInverseBindingDict()
          data, n = [], 0
          for commandName in sorted(c.commandsDict):
              dataList = inverseBindingDict.get(commandName, [('', ''),])
              for z in dataList:
                  pane, key = z
                  pane = '%s ' % (pane) if pane != 'all:' else ''
                  key = k.prettyPrintKey(key).replace('+Key', '')
                  s1 = pane + key
                  s2 = commandName
                  n = max(n, len(s1))
                  data.append((s1, s2),)
          # This isn't perfect in variable-width fonts.
          lines = ['%*s %s\n' % (-n, z1, z2) for z1, z2 in data]
          g.es_print('', ''.join(lines), tabName=tabName)
      #@+node:ekr.20061031131434.122: *4* k.repeatComplexCommand & helper
      @cmd('repeat-complex-command')
      def repeatComplexCommand(self, event):
          '''Repeat the previously executed minibuffer command.'''
          k = self
          if k.mb_history:
              k.setState('last-full-command', 1, handler=k.repeatComplexCommandHelper)
              k.setLabelBlue("Redo: %s" % str(k.mb_history[0]))
          else:
              g.warning('no previous command')
      #@+node:ekr.20131017100903.16689: *5* repeatComplexCommandHelper
      def repeatComplexCommandHelper(self, event):
          k = self; c = k.c
          char = event.char if event else ''
          if char in ('\n', 'Return') and k.mb_history:
              last = k.mb_history[0]
              k.resetLabel()
              k.clearState() # Bug fix.
              c.commandsDict[last](event)
          else:
              k.keyboardQuit()
      #@+node:ekr.20061031131434.123: *4* k.set-xxx-State
      @cmd('set-command-state')
      def setCommandState(self, event):
          '''Enter the 'command' editing state.'''
          k = self
          k.setInputState('command', set_border=True)
          # This command is also valid in headlines.
              # k.c.bodyWantsFocus()
          k.showStateAndMode()
  
      @cmd('set-insert-state')
      def setInsertState(self, event):
          '''Enter the 'insert' editing state.'''
          k = self
          k.setInputState('insert', set_border=True)
          # This command is also valid in headlines.
              # k.c.bodyWantsFocus()
          k.showStateAndMode()
  
      @cmd('set-overwrite-state')
      def setOverwriteState(self, event):
          '''Enter the 'overwrite' editing state.'''
          k = self
          k.setInputState('overwrite', set_border=True)
          # This command is also valid in headlines.
              # k.c.bodyWantsFocus()
          k.showStateAndMode()
      #@+node:ekr.20061031131434.124: *4* k.toggle-input-state
      @cmd('toggle-input-state')
      def toggleInputState(self, event=None):
          '''The toggle-input-state command.'''
          k = self; c = k.c
          default = c.config.getString('top-level-unbound-key-action') or 'insert'
          state = k.unboundKeyAction
          if default == 'insert':
              state = 'command' if state == 'insert' else 'insert'
          elif default == 'overwrite':
              state = 'command' if state == 'overwrite' else 'overwrite'
          else:
              state = 'insert' if state == 'command' else 'command' # prefer insert to overwrite.
          k.setInputState(state)
          k.showStateAndMode()
      #@+node:ekr.20061031131434.125: *3* k.Externally visible helpers
      #@+node:ekr.20140816165728.18968: *4* Wrappers for GetArg methods
      # New in Leo 5.4
      def getNextArg(self, handler):
          '''
          Get the next arg.  For example, after a Tab in the find commands.
          See the docstring for k.get1Arg for examples of its use.
          '''
          # Replace the current handler.
          self.getArgInstance.after_get_arg_state = ('getarg', 1, handler)
          self.c.minibufferWantsFocusNow()
  
      # New in Leo 5.4
      def get1Arg(self, event, handler,
          # returnKind=None, returnState=None,
          prefix=None, tabList=None, completion=True, oneCharacter=False,
          stroke=None, useMinibuffer=True
      ):
          #@+<< docstring for k.get1arg >>
          #@+node:ekr.20161020031633.1: *5* << docstring for k.get1arg >>
          '''
          k.get1Arg: Handle the next character the user types when accumulating
          a user argument from the minibuffer. Ctrl-G will abort this processing
          at any time.
  
          Commands should use k.get1Arg to get the first minibuffer argument and
          k.getNextArg to get all other arguments.
  
          Before going into the many details, let's look at some examples. This
          code will work in any class having a 'c' ivar bound to a commander.
  
          Example 1: get one argument from the user:
  
              @g.command('my-command')
              def myCommand(self, event):
                  k = self.c.k
                  k.setLabelBlue('prompt: ')
                  k.get1Arg(event, handler=self.myCommand1)
  
              def myCommand1(self, event):
                  k = self.c.k
                  # k.arg contains the argument.
                  # Finish the command.
                  ...
                  # Reset the minibuffer.
                  k.clearState()
                  k.resetLabel()
                  k.showStateAndMode()
  
          Example 2: get two arguments from the user:
  
              @g.command('my-command')
              def myCommand(self, event):
                  k = self.c.k
                  k.setLabelBlue('first prompt: ')
                  k.get1Arg(event, handler=self.myCommand1)
  
              def myCommand1(self, event):
                  k = self.c.k
                  self.arg1 = k.arg
                  k.extendLabel(' second prompt: ', select=False, protect=True)
                  k.getNextArg(handler=self.myCommand2)
  
              def myCommand2(self, event):
                  k = self.c.k
                  # k.arg contains second argument.
                  # Finish the command, using self.arg1 and k.arg.
                  ...
                  # Reset the minibuffer.
                  k.clearState()
                  k.resetLabel()
                  k.showStateAndMode()
  
          k.get1Arg and k.getNextArg are a convenience methods. They simply pass
          their arguments to the get_arg method of the singleton GetArg
          instance. This docstring describes k.get1arg and k.getNextArg as if
          they were the corresponding methods of the GetArg class.
  
          k.get1Arg is a state machine. Logically, states are tuples (kind, n,
          handler) though they aren't represented that way. When the state
          machine in the GetArg class is active, the kind is 'getArg'. This
          constant has special meaning to Leo's key-handling code.
  
          The arguments to k.get1Arg are as follows:
  
          event:              The event passed to the command.
  
          handler=None,       An executable. k.get1arg calls handler(event)
                              when the user completes the argument by typing
                              <Return> or (sometimes) <tab>.
  
          tabList=[]:         A list of possible completions.
  
          completion=True:    True if completions are enabled.
  
          oneCharacter=False: True if k.arg should be a single character.
  
          stroke=None:        The incoming key stroke.
  
          useMinibuffer=True: True: put focus in the minibuffer while accumulating arguments.
                              False allows sort-lines, for example, to show the selection range.
  
          '''
          #@-<< docstring for k.get1arg >>
          returnKind, returnState = None, None
          assert handler, g.callers()
          self.getArgInstance.get_arg(event, returnKind, returnState, handler,
              tabList, completion, oneCharacter, stroke, useMinibuffer)
  
      def getArg(self, event,
          returnKind=None, returnState=None, handler=None,
          prefix=None, tabList=None, completion=True, oneCharacter=False,
          stroke=None, useMinibuffer=True
      ):
          '''Convenience method mapping k.getArg to ga.get_arg.'''
          self.getArgInstance.get_arg(event, returnKind, returnState, handler,
              tabList, completion, oneCharacter, stroke, useMinibuffer)
  
      def doBackSpace(self, tabList, completion=True):
          '''Convenience method mapping k.doBackSpace to ga.do_back_space.'''
          self.getArgInstance.do_back_space(tabList, completion)
  
      def doTabCompletion(self, tabList):
          '''Convenience method mapping k.doTabCompletion to ga.do_tab.'''
          self.getArgInstance.do_tab(tabList)
  
      def getMinibufferCommandName(self):
          '''
          Convenience method mapping k.getMinibufferCommandName to
          ga.get_minibuffer_command_name.
          '''
          return self.getArgInstance.get_minibuffer_command_name()
      #@+node:ekr.20061031131434.130: *4* k.keyboardQuit
      @cmd('keyboard-quit')
      def keyboardQuit(self, event=None, setFocus=True, mouseClick=False):
          '''
          This method clears the state and the minibuffer label.
  
          k.endCommand handles all other end-of-command chores.
          '''
          k = self; c = k.c
          if g.app.quitting:
              return
          # 2011/05/30: We may be called from Qt event handlers.
          # Make sure to end editing!
          c.endEditing()
          # Completely clear the mode.
          if setFocus:
              c.frame.log.deleteTab('Mode')
              c.frame.log.hideTab('Completion')
          if k.inputModeName:
              k.endMode()
          # Complete clear the state.
          k.state.kind = None
          k.state.n = None
          k.clearState()
          k.resetLabel()
          if setFocus:
              c.bodyWantsFocus()
          # At present, only the auto-completer suppresses this.
          k.setDefaultInputState()
          if c.vim_mode and c.vimCommands:
              c.vimCommands.reset(setFocus=setFocus)
          else:
              # This was what caused the unwanted scrolling.
              k.showStateAndMode(setFocus=setFocus)
          k.resetCommandHistory()
      #@+node:ekr.20061031131434.126: *4* k.manufactureKeyPressForCommandName
      def manufactureKeyPressForCommandName(self, w, commandName):
          '''
          Implement a command by passing a keypress to the gui.
          
          **Only unit tests use this method.**
          '''
          c, k = self.c, self
          stroke = k.getStrokeForCommandName(commandName)
          assert g.isStroke(stroke), stroke.__class__.__name__
          shortcut = stroke.s
          shortcut = g.checkUnicode(shortcut)
          if shortcut and w:
              g.app.gui.set_focus(c, w)
              g.app.gui.event_generate(c, None, shortcut, w)
          else:
              message = 'no shortcut for %s' % (commandName)
              if g.app.unitTesting:
                  raise AttributeError(message)
              g.error(message)
      #@+node:ekr.20071212104050: *4* k.overrideCommand
      def overrideCommand(self, commandName, func):
          # Override entries in c.k.masterBindingsDict
          k = self
          d = k.masterBindingsDict
          for key in d:
              d2 = d.get(key)
              for key2 in d2:
                  bi = d2.get(key2)
                  if bi.commandName == commandName:
                      bi.func = func
                      d2[key2] = bi
      #@+node:ekr.20061031131434.131: *4* k.registerCommand
      def registerCommand(self, commandName, func,
          allowBinding=False,
          pane='all',
          shortcut=None, # Must be None unless allowBindings is True.
          **kwargs
      ):
          '''
          Make the function available as a minibuffer command.
          
          You can wrap any method in a callback function, so the
          restriction to functions is not significant.
          
          Ignore the 'shortcut' arg unless 'allowBinding' is True.
          
          Only k.bindOpenWith and the mod_scripting.py plugin should set
          allowBinding.
          '''
          c, k = self.c, self
          if not func:
              g.es_print('Null func passed to k.registerCommand\n', commandName)
              return
          f = c.commandsDict.get(commandName)
          if f and f.__name__ != func.__name__:
              g.trace('redefining', commandName, f, '->', func)
          c.commandsDict[commandName] = func
          # Warn about deprecated arguments.
          if shortcut and not allowBinding:
              g.es_print('The "shortcut" keyword arg to k.registerCommand will be ignored')
              g.es_print('Called from', g.callers())
              shortcut = None
          for arg, val in kwargs.items():
              if val is not None:
                  g.es_print('The "%s" keyword arg to k.registerCommand is deprecated' % arg)
                  g.es_print('Called from', g.callers())
          # Make requested bindings, even if a warning has been given.
          # This maintains strict compatibility with existing plugins and scripts.
          k.registerCommandShortcut(
              commandName=commandName,
              func=func,
              pane=pane,
              shortcut=shortcut,
          )
      #@+node:ekr.20171124043747.1: *4* k.registerCommandShortcut
      def registerCommandShortcut(self, commandName, func, pane, shortcut):
          '''
          Register a shortcut for the a command.
          
          **Important**: Bindings created here from plugins can not be overridden.
          This includes @command and @button bindings created by mod_scripting.py.
          '''
          c, k = self.c, self
          is_local = c.shortFileName() not in ('myLeoSettings.leo', 'leoSettings.leo')
          assert not g.isStroke(shortcut)
          if shortcut:
              stroke = g.KeyStroke(binding=shortcut) if shortcut else None
          elif commandName.lower() == 'shortcut': # Causes problems.
              stroke = None
          elif is_local:
              # 327: Don't get defaults when handling a local file.
              stroke = None
          else:
              # Try to get a stroke from leoSettings.leo.
              stroke = None
              junk, aList = c.config.getShortcut(commandName)
              for bi in aList:
                  if bi.stroke and not bi.pane.endswith('-mode'):
                      stroke = bi.stroke
                      pane = bi.pane # 2015/05/11.
                      break
          if stroke:
              k.bindKey(pane, stroke, func, commandName, tag='register-command')
                  # Must be a stroke.
              k.makeMasterGuiBinding(stroke) # Must be a stroke.
          # Fixup any previous abbreviation to press-x-button commands.
          if commandName.startswith('press-') and commandName.endswith('-button'):
              d = c.config.getAbbrevDict()
                  # Keys are full command names, values are abbreviations.
              if commandName in list(d.values()):
                  for key in d:
                      if d.get(key) == commandName:
                          c.commandsDict[key] = c.commandsDict.get(commandName)
                          break
      #@+node:ekr.20061031131434.127: *4* k.simulateCommand & k.commandExists
      def simulateCommand(self, commandName, event=None):
          '''Execute a Leo command by name.'''
          c, k = self.c, self
          func = self.commandExists(commandName)
          if func:
              # Support @g.commander_command
              c_func = getattr(c, func.__name__, None)
              if c_func:
                  return c_func(event=event)
              if event:
                  pass
              elif commandName.startswith('specialCallback'):
                  event = None # A legacy function.
              else: # Create a dummy event as a signal.
                  event = g.app.gui.create_key_event(c)
              k.masterCommand(event=event, func=func)
              if c.exists:
                  return k.funcReturn
              return None
          if g.app.unitTesting:
              raise AttributeError('no such command: %s' % commandName)
          if g.app.inBridge:
              raise AttributeError('no such command: %s' % commandName)
          g.error('simulateCommand: no command for %s' % (commandName))
          return None
      #@+node:ekr.20170324143353.1: *5* k.commandExists
      def commandExists(self, commandName):
          '''Return the command handler for the given command name, or None.'''
          c, k = self.c, self
          commandName = commandName.strip()
          if commandName:
              aList = commandName.split(None)
              if len(aList) == 1:
                  k.givenArgs = []
              else:
                  commandName = aList[0]
                  k.givenArgs = aList[1:]
              func = c.commandsDict.get(commandName)
              return func
          return None
      #@+node:ekr.20140813052702.18203: *4* k.getFileName
      def getFileName(self, event, callback=None,
          filterExt=None, prompt='Enter File Name: ', tabName='Dired'
      ):
          '''Get a file name from the minibuffer.'''
          k = self
          k.fnc.get_file_name(event, callback, filterExt, prompt, tabName)
      #@+node:ekr.20061031131434.145: *3* k.Master event handlers
      #@+node:ekr.20061031131434.146: *4* k.masterKeyHandler & helpers
      def masterKeyHandler(self, event):
          '''The master key handler for almost all key bindings.'''
          k = self
          # Setup...
          if 'keys' in g.app.debug:
              g.trace(repr(k.state.kind), repr(event.char), repr(event.stroke))
          k.checkKeyEvent(event)
          k.setEventWidget(event)
          k.traceVars(event)
          # Order is very important here...
          if k.isSpecialKey(event):
              return
          if k.doKeyboardQuit(event):
              return
          if k.doDemo(event):
              return
          if k.doMode(event):
              return
          if k.doVim(event):
              return
          if k.doUnboundPlainKey(event):
              return
          k.doBinding(event)
              # Calls handleUnboundKeys if no binding.
      #@+node:ekr.20180418040158.1: *5* k.checkKeyEvent
      def checkKeyEvent(self, event):
          '''Perform sanity checks on the incoming event.'''
          # These assert's should be safe, because eventFilter
          # calls k.masterKeyHandler inside a try/except block.
          c = self.c
          assert event is not None
          c.check_event(event)
          assert hasattr(event, 'char')
          assert hasattr(event, 'stroke')
          if not hasattr(event, 'widget'):
              event.widget = None
          assert g.isStrokeOrNone(event.stroke)
          if event:
              assert event.stroke.s not in g.app.gui.ignoreChars, repr(event.stroke.s)
                  # A continuous unit test, better than "@test k.isPlainKey".
  
      #@+node:ekr.20180418033838.1: *5* k.doBinding
      def doBinding(self, event):
          '''
          The last phase of k.masertKeyHandler.
          Execute the command associated with stroke's binding.
          Call k.handleUnboundKeys for killed or non-existent bindings.
          '''
          c, k = self.c, self
          char, stroke, w = event.char, event.stroke, event.widget
          #
          # Use getPaneBindings for *all* keys.
          #
          bi = k.getPaneBinding(stroke, w)
          #
          # Call k.handleUnboudKeys for all killed bindings.
          #
          if bi and bi.commandName in k.killedBindings:
              #327: ignore killed bindings.
              k.handleUnboundKeys(event)
              return
          #
          # Call k.masterCommandHandler if the binding exists.
          #
          if bi:
              k.masterCommand(
                  event=event,
                  commandName=bi.commandName,
                  func=bi.func,
                  stroke=bi.stroke)
              return
          #
          # Handle unbound keys in the tree (not headlines).
          # 
          if c.widget_name(w).startswith('canvas'):
              k.searchTree(char)
              return
          #
          # No binding exists. Call k.handleUnboundKey.
          #
          k.handleUnboundKeys(event)
      #@+node:ekr.20180418023827.1: *5* k.doDemo
      def doDemo(self, event):
          '''
          Support the demo.py plugin.
          Return True if k.masterKeyHandler should return.
          '''
          k = self
          stroke = event.stroke
          demo = getattr(g.app, 'demo', None)
          if not demo:
              return False
          #
          # Shortcut everything so that demo-next or demo-prev won't alter of our ivars.
          if k.demoNextKey and stroke == k.demoNextKey:
              if demo.trace:
                  g.trace('demo-next', stroke)
              demo.next_command()
              return True
          if k.demoPrevKey and stroke == k.demoPrevKey:
              if demo.trace:
                  g.trace('demo-prev', stroke)
              demo.prev_command()
              return True
          return False
      #@+node:ekr.20061031131434.108: *5* k.callStateFunction
      def callStateFunction(self, event):
          '''Call the state handler associated with this event.'''
          k = self
          ch = event.char
          #
          # Defensive programming
          #
          if not k.state.kind:
              return None
          if not k.state.handler:
              g.error('callStateFunction: no state function for', k.state.kind)
              return None
          #
          # Handle auto-completion before checking for unbound keys.
          #
          if k.state.kind == 'auto-complete':
              # k.auto_completer_state_handler returns 'do-standard-keys' for control keys.
              val = k.state.handler(event)
              return val
          #
          # Ignore unbound non-ascii keys.
          #
          if (
              k.ignore_unbound_non_ascii_keys and
              len(ch) == 1 and
              ch and ch not in ('\b', '\n', '\r', '\t') and
              (ord(ch) < 32 or ord(ch) > 128)
          ):
              return None
          #
          # Call the state handler.
          #
          val = k.state.handler(event)
          if val != 'continue':
              k.endCommand(k.commandName)
          return val
      #@+node:ekr.20180418025702.1: *5* k.doUnboundPlainKey & helper
      def doUnboundPlainKey(self, event):
          '''
          Handle unbound plain keys.
          Return True if k.masterKeyHandler should return.
          '''
          c, k = self.c, self
          stroke, w = event.stroke, event.widget
          #
          # Ignore non-plain keys.
          if not k.isPlainKey(stroke):
              return False
          #
          # Ignore any keys in the background tree widget.
          if c.widget_name(w).startswith('canvas'):
              return False
          #
          # Ignore the char if it is bound to the auto-complete command.
          if self.isAutoCompleteChar(stroke):
              return False
          #
          # Handle the unbound key.
          k.handleUnboundKeys(event)
          return True
      #@+node:ekr.20110209083917.16004: *6* k.isAutoCompleteChar
      def isAutoCompleteChar(self, stroke):
          '''
          Return True if stroke is bound to the auto-complete in
          the insert or overwrite state.
          '''
          k = self; state = k.unboundKeyAction
          assert g.isStrokeOrNone(stroke)
          if stroke and state in ('insert', 'overwrite'):
              for key in (state, 'body', 'log', 'text', 'all'):
                  d = k.masterBindingsDict.get(key, {})
                  if d:
                      bi = d.get(stroke)
                      if bi:
                          assert bi.stroke == stroke, 'bi: %s stroke: %s' % (bi, stroke)
                          if bi.commandName == 'auto-complete':
                              return True
          return False
      #@+node:ekr.20180418025241.1: *5* k.doVim
      def doVim(self, event):
          '''
          Handle vim mode.
          Return True if k.masterKeyHandler should return.
          '''
          c = self.c
          if c.vim_mode and c.vimCommands:
              ok = c.vimCommands.do_key(event)
              return ok
          return False
      #@+node:ekr.20091230094319.6244: *5* k.doMode
      def doMode(self, event):
          '''
          Handle mode bindings.
          Return True if k.masterKeyHandler should return.
          '''
          k = self
          state = k.state.kind
          stroke = event.stroke
          if not k.inState():
              return False
          #
          # First, honor minibuffer bindings for all except user modes.
          if state == 'input-shortcut':
              k.handleInputShortcut(event, stroke)
              return True
          if state in ('getArg', 'getFileName', 'full-command', 'auto-complete', 'vim-mode'):
              if k.handleMiniBindings(event, state, stroke):
                  return True
          #
          # Second, honor general modes.
          #
          if state == 'getArg':
              # New in Leo 5.8: Only call k.getArg for keys it can handle.
              if k.isPlainKey(stroke):
                  k.getArg(event, stroke=stroke)
                  return True
              if stroke.s in ('Escape', 'Tab', 'BackSpace'):
                  k.getArg(event, stroke=stroke)
                  return True
              return False 
          if state in ('getFileName', 'get-file-name'):
              k.getFileName(event)
              return True
          if state in ('full-command', 'auto-complete'):
              val = k.callStateFunction(event)
                  # Do the default state action.
                  # Calls end-command.
              return val != 'do-standard-keys'
          #
          # Third, pass keys to user modes.
          #
          d = k.masterBindingsDict.get(state)
          if d:
              assert g.isStrokeOrNone(stroke)
              bi = d.get(stroke)
              if bi:
                  # Bound keys continue the mode.
                  k.generalModeHandler(event,
                      commandName=bi.commandName,
                      func=bi.func,
                      modeName=state,
                      nextMode=bi.nextMode)
                  return True
              # Unbound keys end mode.
              k.endMode()
              return False
          #
          # Fourth, call the state handler.
          #
          handler = k.getStateHandler()
          if handler:
              handler(event)
          return True
      #@+node:ekr.20091230094319.6240: *5* k.getPaneBinding & helper
      def getPaneBinding(self, stroke, w):
         
          k = self
          if not g.assert_is(stroke, g.KeyStroke):
              return None
          for key, name in (
              # Order here is similar to bindtags order.
              ('command', None),
              ('insert', None),
              ('overwrite', None),
              ('button', None),
              ('body', 'body'),
              ('text', 'head'), # Important: text bindings in head before tree bindings.
              ('tree', 'head'),
              ('tree', 'canvas'),
              ('log', 'log'),
              ('text', 'log'),
              ('text', None),
              ('all', None),
          ):
              val = k.getBindingHelper(key, name, stroke, w)
              if val:
                  return val
          return None
      #@+node:ekr.20180418105228.1: *6* getPaneBindingHelper
      def getBindingHelper(self, key, name, stroke, w):
          '''Find a binding for the widget with the given name.'''
          c, k = self.c, self
          #
          # Return if the pane's name doesn't match the event's widget.
          state = k.unboundKeyAction
          w_name = c.widget_name(w)
          pane_matches = (
              name and w_name.startswith(name) or
              key in ('command', 'insert', 'overwrite') and state == key or
              key in ('text', 'all') and g.isTextWrapper(w) or
              key in ('button', 'all')
          )
          if not pane_matches:
              return None
          #
          # Return if there is no binding at all.
          d = k.masterBindingsDict.get(key, {})
          if not d:
              return None
          bi = d.get(stroke)
          if not bi:
              return None
          #
          # Ignore previous/next-line commands while editing headlines.
          if (
              key == 'text' and
              name == 'head' and
              bi.commandName in ('previous-line', 'next-line')
          ):
              return None
          #
          # The binding has been found.
          return bi
      #@+node:ekr.20061031131434.110: *5* k.handleDefaultChar
      def handleDefaultChar(self, event, stroke):
          '''
          Handle an unbound key, based on the event's widget.
          Do not assume that stroke exists.
          '''
          c, k, w = self.c, self, event.widget
          name = c.widget_name(w)
          #
          # Ignore unbound alt-ctrl key
          if stroke and stroke.isAltCtrl() and k.ignore_unbound_non_ascii_keys:
              g.app.unitTestDict['handleUnboundChar-ignore-alt-or-ctrl'] = True
              return
          #
          # Handle events in the body pane.
          if name.startswith('body'):
              action = k.unboundKeyAction
              if action in ('insert', 'overwrite'):
                  c.editCommands.selfInsertCommand(event, action=action)
              else:
                  pass # Ignore the key
              return
          #
          # Handle events in headlines.
          if name.startswith('head'):
              c.frame.tree.onHeadlineKey(event)
              return
          #
          # Handle events in the background tree.
          if name.startswith('canvas'):
              if not stroke: # Not exactly right, but it seems to be good enough.
                  c.onCanvasKey(event)
              return
          #
          # Handle events in the log pane.
          if name.startswith('log'):
              # Make sure we can insert into w.
              log_w = event.widget
              if not hasattr(log_w, 'supportsHighLevelInterface'):
                  return
              # Send the event to the text widget, not the LeoLog instance.
              if not stroke:
                  stroke = event.stroke
              if stroke:
                  i = log_w.getInsertPoint()
                  s = stroke.toGuiChar()
                  log_w.insert(i, s)
              return
          #
          # Ignore all other events.
      #@+node:vitalije.20170708161511.1: *5* k.handleInputShortcut
      def handleInputShortcut(self, event, stroke):
          c, k, p = self.c, self, self.c.p
          k.clearState()
          if p.h.startswith(('@shortcuts', '@mode')):
              # line of text in body
              w = c.frame.body
              before, sel, after = w.getInsertLines()
              m = k._cmd_handle_input_pattern.search(sel)
              assert m # edit-shortcut was invoked on a malformed body line
              sel = '%s %s' % (m.group(0), stroke.s)
              udata = c.undoer.beforeChangeNodeContents(p)
              w.setSelectionAreas(before, sel, after)
              c.undoer.afterChangeNodeContents(p, 'change shortcut', udata)
              w.onBodyChanged('change shortcut')
              cmdname = m.group(0).rstrip('= ')
              k.editShortcut_do_bind_helper(stroke, cmdname)
              return
          if p.h.startswith(('@command', '@button')):
              udata = c.undoer.beforeChangeNodeContents(p)
              cmd = p.h.split('@key',1)[0]
              p.h = '%s @key=%s' % (cmd, stroke.s)
              c.undoer.afterChangeNodeContents(p, 'change shortcut', udata)
              try:
                  cmdname = cmd.split(' ', 1)[1].strip()
                  k.editShortcut_do_bind_helper(stroke, cmdname)
              except IndexError:
                  pass
              return
          # this should never happen
          g.error('not in settings node shortcut')
      #@+node:vitalije.20170709151653.1: *6* k.isInShortcutBodyLine
      _cmd_handle_input_pattern = re.compile(r'[A-Za-z0-9_\-]+\s*=')
  
      def isInShortcutBodyLine(self):
          k = self; c = k.c; p = c.p
          if p.h.startswith(('@shortcuts', '@mode')):
              # line of text in body
              w = c.frame.body
              before, sel, after = w.getInsertLines()
              m = k._cmd_handle_input_pattern.search(sel)
              return bool(m)
          return p.h.startswith(('@command', '@button'))
      #@+node:vitalije.20170709151658.1: *6* k.isEditShortcutSensible
      def isEditShortcutSensible(self):
          k = self; c = k.c; p = c.p
          return p.h.startswith(('@command', '@button')) or k.isInShortcutBodyLine()
      #@+node:vitalije.20170709202924.1: *6* k.editShortcut_do_bind_helper
      def editShortcut_do_bind_helper(self, stroke, cmdname):
          k = self; c = k.c
          cmdfunc = c.commandsDict.get(cmdname)
          if cmdfunc:
              k.bindKey('all', stroke, cmdfunc, cmdname)
              g.es('bound', stroke, 'to command', cmdname)
      #@+node:ekr.20061031131434.152: *5* k.handleMiniBindings
      def handleMiniBindings(self, event, state, stroke):
          '''Find and execute commands bound to the event.'''
          k = self
          #
          # Special case for bindings handled in k.getArg:
          if state == 'full-command' and stroke in ('Up', 'Down'):
              return False
          #
          # Ignore other special keys in the minibuffer.
          if state in ('getArg', 'full-command'):
              if stroke in (
                  '\b', 'BackSpace',
                  '\r', 'Linefeed',
                  '\n', 'Return',
                  '\t', 'Tab',
                  'Escape',
              ):
                  return False
              if k.isFKey(stroke):
                  return False
          #
          # Ignore autocompletion state.
          if state.startswith('auto-'):
              return False
          # 
          # Ignore plain key binding in the minibuffer.
          if not stroke or k.isPlainKey(stroke):
              return False
          #
          # Get the command, based on the pane.
          for pane in ('mini', 'all', 'text'):
              result = k.handleMinibufferHelper(event, pane, state, stroke)
              assert result in ('continue', 'found', 'ignore')
              if result == 'ignore':
                  return False # Let getArg handle it.
              if result == 'found':
                  # Do not call k.keyboardQuit here!
                  return True
          #
          # No binding exists.
          return False
      #@+node:ekr.20180418114300.1: *6* k.handleMinibufferHelper
      def handleMinibufferHelper(self, event, pane, state, stroke):
          '''
          Execute a pane binding in the minibuffer.
          
          Return 'continue', 'ignore', 'found'
          '''
          c, k = self.c, self
          d = k.masterBindingsDict.get(pane)
          if not d:
              return 'continue'
          bi = d.get(stroke)
          if not bi:
              return 'continue'
          assert bi.stroke == stroke, 'bi: %s stroke: %s' % (bi, stroke)
          #
          # Special case the replace-string command in the minibuffer.
          #
          if bi.commandName == 'replace-string' and state == 'getArg':
              return 'ignore'
          #
          # Execute this command.
          #
          if bi.commandName not in k.singleLineCommandList:
              k.keyboardQuit()
          else:
              c.minibufferWantsFocus() # New in Leo 4.5.
          # Pass this on for macro recording.
          k.masterCommand(
              commandName=bi.commandName,
              event=event,
              func=bi.func,
              stroke=stroke)
          # Careful: the command could exit.
          if c.exists and not k.silentMode:
              # Use the state *after* executing the command.
              if k.state.kind:
                  c.minibufferWantsFocus()
              else:
                  c.bodyWantsFocus()
          return 'found'
      #@+node:ekr.20180418031118.1: *5* k.isSpecialKey
      def isSpecialKey(self, event):
          '''Return True if char is a special key.'''
          if not event:
              # An empty event is not an error.
              return False
          # Fix #917.
          if len(event.char) > 1 and not event.stroke.s:
              # stroke.s was cleared, but not event.char.
              return True
          return event.char in g.app.gui.ignoreChars
      #@+node:ekr.20180418024449.1: *5* k.keyboardQuit
      def doKeyboardQuit(self, event):
          '''
          Handle keyboard-quit logic.
          return True if k.masterKeyHandler should return.
          '''
          c, k = self.c, self
          stroke = event.stroke
          if k.abortAllModesKey and stroke == k.abortAllModesKey:
              if getattr(c, 'screenCastController', None):
                  c.screenCastController.quit()
              k.masterCommand(
                  commandName='keyboard-quit',
                  event=event,
                  func=k.keyboardQuit,
                  stroke=stroke)
              return True
          return False
      #@+node:ekr.20080510095819.1: *5* k.handleUnboundKeys
      def handleUnboundKeys(self, event):
         
          c, k = self.c, self
          stroke = event.stroke
          if not g.assert_is(stroke, g.KeyStroke):
              return
          #
          # Ignore all unbound characters in command mode.
          if k.unboundKeyAction == 'command':
              w = g.app.gui.get_focus(c)
              if w and g.app.gui.widget_name(w).lower().startswith('canvas'):
                  c.onCanvasKey(event)
              return
          #
          # Ignore unbound F-keys.
          if stroke.isFKey():
              return
          #
          #  Handle a normal character in insert/overwrite.
          # <Return> is *not* a normal character.
          if (
              stroke and k.isPlainKey(stroke) and
              k.unboundKeyAction in ('insert', 'overwrite')
          ):
              k.masterCommand(event=event, stroke=stroke)
              return
          #
          # Always ignore unbound Alt/Ctrl keys.
          if stroke.isAltCtrl() and not self.enable_alt_ctrl_bindings:
              return
          # #868
          if stroke.isPlainNumPad():
              stroke.removeNumPadModifier()
              k.masterCommand(event=event, stroke=stroke)
              return
          # #868
          if stroke.isNumPadKey():
              # To have effect, these must be bound.
              return
          #
          # Ignore unbound non-ascii character.
          if (
              k.ignore_unbound_non_ascii_keys and
              not stroke.isPlainKey()
          ):
              return
          #
          # Never insert escape or insert characters.
          if (
              stroke and stroke.find('Escape') != -1 or
              stroke and stroke.find('Insert') != -1
          ):
              return
          #
          # Let k.masterCommand handle the unbound character.
          k.masterCommand(event=event, stroke=stroke)
      #@+node:ekr.20061031131434.105: *5* k.masterCommand
      def masterCommand(self, commandName=None, event=None, func=None, stroke=None):
          '''
          This is the central dispatching method.
          All commands and keystrokes pass through here.
          This returns None, but may set k.funcReturn.
          '''
          c, k = self.c, self
          if event: c.check_event(event)
          c.setLog()
          k.stroke = stroke # Set this global for general use.
          ch = event.char if event else ''
          #
          # Ignore all special keys.
          if k.isSpecialKey(event):
              return
          #
          # Compute func if not given.
          # It is *not* an error for func to be None.
          if commandName and not func:
              func = c.commandsDict.get(commandName.replace('&', ''))
              if not func:
                  g.es_print('no command for @item %r' % (commandName), color='red')
                  return
          commandName = commandName or func and func.__name__ or '<no function>'
          if 'keys' in g.app.debug:
              # A very important trace.
              g.trace(commandName, 'stroke', stroke)
          k.funcReturn = None # For unit testing.
          #
          # Remember the key.
          k.setLossage(ch, stroke)
          #
          # Handle keyboard-quit.
          if k.abortAllModesKey and stroke == k.abortAllModesKey:
              k.keyboardQuit()
              k.endCommand(commandName)
              return
          #
          # Ignore abbreviations.
          if k.abbrevOn and c.abbrevCommands.expandAbbrev(event, stroke):
              return
          #
          # Handle the func argument, if given.
          if func:
              if commandName.startswith('specialCallback'):
                  # The callback function will call c.doCommand.
                  val = func(event)
                  # Set k.funcReturn for k.simulateCommand..
                  k.funcReturn = k.funcReturn or val
              else:
                  # Call c.doCommand directly.
                  c.doCommand(func, commandName, event=event)
              if c.exists:
                  k.endCommand(commandName)
                  c.frame.updateStatusLine()
              return
          #
          # Ignore unbound keys in a state.
          if k.inState():
              return
          #
          # Finally, call k.handleDefaultChar.
          k.handleDefaultChar(event, stroke)
          if c.exists:
              c.frame.updateStatusLine()
          
      #@+node:ekr.20180418034305.1: *5* k.setEventWidget
      def setEventWidget(self, event):
          '''
          A hack: redirect the event to the text part of the log.
          '''
          c = self.c
          w = event.widget
          w_name = c.widget_name(w)
          if w_name.startswith('log'):
              event.widget = c.frame.log.logCtrl
      #@+node:ekr.20180418031417.1: *5* k.traceVars
      def traceVars(self, event):
          
          trace = False and not g.unitTesting
          traceGC = False
          verbose = False
          k = self
          if not trace:
              return
          if traceGC:
              g.printNewObjects('masterKey 1')
          if verbose:
              char = event.char
              state = k.state.kind
              stroke = event.stroke
              g.trace('stroke: %r, char: %r, state: %s, state2: %s' % (
                  stroke, char, state, k.unboundKeyAction))
      #@+node:ekr.20160409035115.1: *5* k.searchTree
      def searchTree(self, char):
          '''Search all visible nodes for a headline starting with stroke.'''
          if not char: return
          c = self.c
          if not c.config.getBool('plain-key-outline-search'):
              return
  
          def match(p):
              '''Return True if p contains char.'''
              s = p.h.lower() if char.islower() else p.h
              return s.find(char) > -1
  
          # Start at c.p, then retry everywhere.
          for p in (c.p, c.rootPosition()):
              p = p.copy()
              if p == c.p and match(p):
                  p.moveToVisNext(c)
              while p:
                  if match(p):
                      c.selectPosition(p)
                      c.redraw()
                      return
                  p.moveToVisNext(c)
  
          # Too confusing for the user.
          # re_pat = re.compile(r'^@(\w)+[ \t](.+)')
  
          # def match(p, pattern):
              # s = p.h.lower()
              # if pattern:
                  # m = pattern.search(s)
                  # found = (s.startswith(char) or
                      # m and m.group(2).lower().startswith(char))
              # else:
                  # found = s.find(char) > -1
              # if found:
                  # c.selectPosition(p)
                  # c.redraw()
              # return found
      #@+node:ekr.20061031170011.3: *3* k.Minibuffer
      # These may be overridden, but this code is now gui-independent.
      #@+node:ekr.20061031170011.9: *4* k.extendLabel
      def extendLabel(self, s, select=False, protect=False):
  
          k = self; c = k.c; w = self.w
          if not (w and s):
              return
          c.widgetWantsFocusNow(w)
          w.insert('end', s)
          if select:
              i, j = k.getEditableTextRange()
              w.setSelectionRange(i, j, insert=j)
          if protect:
              k.protectLabel()
      #@+node:ekr.20061031170011.13: *4* k.getEditableTextRange
      def getEditableTextRange(self):
          k = self; w = self.w
          s = w.getAllText()
          i = len(k.mb_prefix)
          j = len(s)
          return i, j
      #@+node:ekr.20061031170011.5: *4* k.getLabel
      def getLabel(self, ignorePrompt=False):
          k = self; w = self.w
          if not w: return ''
          s = w.getAllText()
          if ignorePrompt:
              return s[len(k.mb_prefix):]
          return s or ''
      #@+node:ekr.20080408060320.791: *4* k.killLine
      def killLine(self, protect=True):
          k = self
          w = k.w
          s = w.getAllText()
          s = s[: len(k.mb_prefix)]
          w.setAllText(s)
          n = len(s)
          w.setSelectionRange(n, n, insert=n)
          if protect:
              k.mb_prefix = s
      #@+node:ekr.20061031131434.135: *4* k.minibufferWantsFocus
      # def minibufferWantsFocus(self):
          # c = self.c
          # c.widgetWantsFocus(c.miniBufferWidget)
      #@+node:ekr.20061031170011.6: *4* k.protectLabel
      def protectLabel(self):
          k = self; w = self.w
          if not w: return
          k.mb_prefix = w.getAllText()
      #@+node:ekr.20061031170011.7: *4* k.resetLabel
      def resetLabel(self):
          '''Reset the minibuffer label.'''
          k = self
          c, w = k.c, k.w
          k.setLabelGrey('')
          k.mb_prefix = ''
          if w:
              w.setSelectionRange(0, 0, insert=0)
              state = k.unboundKeyAction
              if c.vim_mode and c.vimCommands:
                  c.vimCommands.show_status()
              else:
                  k.setLabelBlue(label='%s State' % (state.capitalize()))
      #@+node:ekr.20080408060320.790: *4* k.selectAll
      def selectAll(self):
          '''Select all the user-editable text of the minibuffer.'''
          w = self.w
          i, j = self.getEditableTextRange()
          w.setSelectionRange(i, j, insert=j)
      #@+node:ekr.20061031170011.8: *4* k.setLabel
      def setLabel(self, s, protect=False):
          '''Set the label of the minibuffer.'''
          c, k, w = self.c, self, self.w
          if w:
              # Support for the curses gui.
              if hasattr(g.app.gui, 'set_minibuffer_label'):
                  g.app.gui.set_minibuffer_label(c, s)
              w.setAllText(s)
              n = len(s)
              w.setSelectionRange(n, n, insert=n)
              if protect:
                  k.mb_prefix = s
      #@+node:ekr.20061031170011.10: *4* k.setLabelBlue
      def setLabelBlue(self, label, protect=True):
          '''Set the minibuffer label.'''
          k, w = self, self.w
          if hasattr(g.app.gui, 'set_minibuffer_label'):
              g.app.gui.set_minibuffer_label(self.c, label)
          elif w:
              w.setStyleClass('') # normal state, not warning or error
              if label is not None:
                  k.setLabel(label, protect=protect)
      #@+node:ekr.20061031170011.11: *4* k.setLabelGrey
      def setLabelGrey(self, label=None):
          k = self; w = self.w
          if not w: return
          w.setStyleClass('minibuffer_warning')
          if label is not None:
              k.setLabel(label)
  
      setLabelGray = setLabelGrey
      #@+node:ekr.20080510153327.2: *4* k.setLabelRed
      def setLabelRed(self, label=None, protect=False):
          k = self; w = self.w
          if not w: return
          w.setStyleClass('minibuffer_error')
          if label is not None:
              k.setLabel(label, protect)
      #@+node:ekr.20140822051549.18298: *4* k.setStatusLabel
      def setStatusLabel(self, s):
          '''
          Set the label to s.
  
          Use k.setStatusLabel, not k.setLael, to report the status of a Leo
          command. This allows the option to use g.es instead of the minibuffer
          to report status.
          '''
          k = self
          k.setLabel(s, protect=False)
      #@+node:ekr.20061031170011.12: *4* k.updateLabel
      def updateLabel(self, event):
          '''Mimic what would happen with the keyboard and a Text editor
          instead of plain accumulation.'''
          k = self; c = k.c; w = self.w
          ch = event.char if event else ''
          if ch and ch not in ('\n', '\r'):
              c.widgetWantsFocusNow(w)
              i, j = w.getSelectionRange()
              ins = w.getInsertPoint()
              if i != j:
                  w.delete(i, j)
              if ch == '\b':
                  s = w.getAllText()
                  if len(s) > len(k.mb_prefix):
                      w.delete(i - 1)
                      i -= 1
              else:
                  w.insert(ins, ch)
                  i = ins + 1
      #@+node:ekr.20120208064440.10190: *3* k.Modes (no change)
      #@+node:ekr.20061031131434.100: *4* k.addModeCommands (enterModeCallback)
      def addModeCommands(self):
          '''Add commands created by @mode settings to c.commandsDict.'''
          k = self; c = k.c
          d = g.app.config.modeCommandsDict # Keys are command names: enter-x-mode.
          # Create the callback functions and update c.commandsDict.
          for key in d.keys():
              # pylint: disable=cell-var-from-loop
  
              def enterModeCallback(event=None, name=key):
                  k.enterNamedMode(event, name)
  
              c.commandsDict[key] = enterModeCallback
      #@+node:ekr.20061031131434.157: *4* k.badMode
      def badMode(self, modeName):
          k = self
          k.clearState()
          if modeName.endswith('-mode'): modeName = modeName[: -5]
          k.setLabelGrey('@mode %s is not defined (or is empty)' % modeName)
      #@+node:ekr.20061031131434.158: *4* k.createModeBindings
      def createModeBindings(self, modeName, d, w):
          '''Create mode bindings for the named mode using dictionary d for w, a text widget.'''
          k = self; c = k.c
          assert d.name().endswith('-mode')
          for commandName in d.keys():
              if commandName in ('*entry-commands*', '*command-prompt*'):
                  # These are special-purpose dictionary entries.
                  continue
              func = c.commandsDict.get(commandName)
              if not func:
                  g.es_print('no such command:', commandName, 'Referenced from', modeName)
                  continue
              aList = d.get(commandName, [])
              for bi in aList:
                  stroke = bi.stroke
                  # Important: bi.val is canonicalized.
                  if stroke and stroke not in ('None', 'none', None):
                      assert g.isStroke(stroke)
                      k.makeMasterGuiBinding(stroke)
                      # Create the entry for the mode in k.masterBindingsDict.
                      # Important: this is similar, but not the same as k.bindKeyToDict.
                      # Thus, we should **not** call k.bindKey here!
                      d2 = k.masterBindingsDict.get(modeName, {})
                      d2[stroke] = g.BindingInfo(
                          kind='mode<%s>' % (modeName), # 2012/01/23
                          commandName=commandName,
                          func=func,
                          nextMode=bi.nextMode,
                          stroke=stroke)
                      k.masterBindingsDict[modeName] = d2
      #@+node:ekr.20120208064440.10179: *4* k.endMode
      def endMode(self):
          k = self; c = k.c
          w = g.app.gui.get_focus(c)
          if w:
              c.frame.log.deleteTab('Mode') # Changes focus to the body pane
          k.endCommand(k.stroke)
          k.inputModeName = None
          k.clearState()
          k.resetLabel()
          k.showStateAndMode() # Restores focus.
          if w:
              c.widgetWantsFocusNow(w)
      #@+node:ekr.20061031131434.160: *4* k.enterNamedMode
      def enterNamedMode(self, event, commandName):
          k = self; c = k.c
          modeName = commandName[6:]
          c.inCommand = False # Allow inner commands in the mode.
          k.generalModeHandler(event, modeName=modeName)
      #@+node:ekr.20061031131434.161: *4* k.exitNamedMode
      @cmd('exit-named-mode')
      def exitNamedMode(self, event=None):
          '''Exit an input mode.'''
          k = self
          if k.inState():
              k.endMode()
          k.showStateAndMode()
      #@+node:ekr.20061031131434.165: *4* k.modeHelp & helper (revise helper)
      @cmd('mode-help')
      def modeHelp(self, event):
          '''The mode-help command.
  
          A possible convention would be to bind <Tab> to this command in most modes,
          by analogy with tab completion.'''
          k = self; c = k.c
          c.endEditing()
          if k.inputModeName:
              d = g.app.config.modeCommandsDict.get('enter-' + k.inputModeName)
              k.modeHelpHelper(d)
          if not k.silentMode:
              c.minibufferWantsFocus()
      #@+node:ekr.20061031131434.166: *5* modeHelpHelper
      def modeHelpHelper(self, d):
          k = self; c = k.c; tabName = 'Mode'
          c.frame.log.clearTab(tabName)
          data, n = [], 0
          for key in sorted(d.keys()):
              if key in ('*entry-commands*', '*command-prompt*'):
                  pass
              else:
                  aList = d.get(key)
                  for bi in aList:
                      stroke = bi.stroke
                      if stroke not in (None, 'None'):
                          s1 = key
                          s2 = k.prettyPrintKey(stroke)
                          n = max(n, len(s1))
                          data.append((s1, s2),)
          data.sort()
          modeName = k.inputModeName.replace('-', ' ')
          if modeName.endswith('mode'):
              modeName = modeName[: -4].strip()
          prompt = d.get('*command-prompt*')
          if prompt:
              g.es('', '%s\n\n' % (prompt.kind.strip()), tabName=tabName)
          else:
              g.es('', '%s mode\n\n' % modeName, tabName=tabName)
          # This isn't perfect in variable-width fonts.
          for s1, s2 in data:
              g.es('', '%*s %s' % (n, s1, s2), tabName=tabName)
      #@+node:ekr.20061031131434.164: *4* k.reinitMode (call k.createModeBindings???)
      def reinitMode(self, modeName):
          k = self
          d = k.modeBindingsDict
          k.inputModeName = modeName
          w = k.modeWidget if k.silentMode else k.w
          k.createModeBindings(modeName, d, w)
          if k.silentMode:
              k.showStateAndMode()
          else:
              # Do not set the status line here.
              k.setLabelBlue(modeName + ': ') # ,protect=True)
      #@+node:ekr.20120208064440.10199: *4* k.generalModeHandler (OLD)
      def generalModeHandler(self, event,
          commandName=None,
          func=None,
          modeName=None,
          nextMode=None,
          prompt=None
      ):
          '''Handle a mode defined by an @mode node in leoSettings.leo.'''
          k = self; c = k.c
          state = k.getState(modeName)
          if state == 0:
              k.inputModeName = modeName
              k.modePrompt = prompt or modeName
              k.modeWidget = event and event.widget
              k.setState(modeName, 1, handler=k.generalModeHandler)
              self.initMode(event, modeName)
              # Careful: k.initMode can execute commands that will destroy a commander.
              if g.app.quitting or not c.exists: return
              if not k.silentMode:
                  if c.config.getBool('showHelpWhenEnteringModes'):
                      k.modeHelp(event)
                  else:
                      c.frame.log.hideTab('Mode')
          elif not func:
              g.trace('No func: improper key binding')
          else:
              if commandName == 'mode-help':
                  func(event)
              else:
                  self.endMode()
                  # New in 4.4.1 b1: pass an event describing the original widget.
                  if event:
                      event.w = event.widget = k.modeWidget
                  else:
                      event = g.app.gui.create_key_event(c, w=k.modeWidget)
                  func(event)
                  if g.app.quitting or not c.exists:
                      pass
                  elif nextMode in (None, 'none'):
                      # Do *not* clear k.inputModeName or the focus here.
                      # func may have put us in *another* mode.
                      pass
                  elif nextMode == 'same':
                      silent = k.silentMode
                      k.setState(modeName, 1, handler=k.generalModeHandler)
                      self.reinitMode(modeName) # Re-enter this mode.
                      k.silentMode = silent
                  else:
                      k.silentMode = False # All silent modes must do --> set-silent-mode.
                      self.initMode(event, nextMode) # Enter another mode.
      #@+node:ekr.20061031131434.156: *3* k.Modes
      #@+node:ekr.20061031131434.163: *4* k.initMode
      def initMode(self, event, modeName):
  
          k = self; c = k.c
          if not modeName:
              g.trace('oops: no modeName')
              return
          d = g.app.config.modeCommandsDict.get('enter-' + modeName)
          if not d:
              self.badMode(modeName)
              return
          k.modeBindingsDict = d
          bi = d.get('*command-prompt*')
          prompt = bi.kind if bi else modeName
          k.inputModeName = modeName
          k.silentMode = False
          aList = d.get('*entry-commands*', [])
          if aList:
              for bi in aList:
                  commandName = bi.commandName
                  k.simulateCommand(commandName)
                  # Careful, the command can kill the commander.
                  if g.app.quitting or not c.exists: return
                  # New in Leo 4.5: a startup command can immediately transfer to another mode.
                  if commandName.startswith('enter-'):
                      return
          # Create bindings after we know whether we are in silent mode.
          w = k.modeWidget if k.silentMode else k.w
          k.createModeBindings(modeName, d, w)
          k.showStateAndMode(prompt=prompt)
      #@+node:ekr.20120208064440.10201: *4* k.NEWgeneralModeHandler (NEW MODES)
      def NEWgeneralModeHandler(self, event,
          commandName=None,
          func=None,
          modeName=None,
          nextMode=None,
          prompt=None
      ):
          '''Handle a mode defined by an @mode node in leoSettings.leo.'''
          k = self; c = k.c
          state = k.getState(modeName)
          if state == 0:
              k.inputModeName = modeName
              k.modePrompt = prompt or modeName
              k.modeWidget = event and event.widget
              k.setState(modeName, 1, handler=k.generalModeHandler)
              self.initMode(event, modeName)
              # Careful: k.initMode can execute commands that will destroy a commander.
              if g.app.quitting or not c.exists: return
              if not k.silentMode:
                  if c.config.getBool('showHelpWhenEnteringModes'):
                      k.modeHelp(event)
                  else:
                      c.frame.log.hideTab('Mode')
          elif not func:
              g.trace('No func: improper key binding')
          else:
              if commandName == 'mode-help':
                  func(event)
              else:
                  self.endMode()
                  # New in 4.4.1 b1: pass an event describing the original widget.
                  if event:
                      event.w = event.widget = k.modeWidget
                  else:
                      event = g.app.gui.create_key_event(c, w=k.modeWidget)
                  func(event)
                  if g.app.quitting or not c.exists:
                      pass
                  elif nextMode in (None, 'none'):
                      # Do *not* clear k.inputModeName or the focus here.
                      # func may have put us in *another* mode.
                      pass
                  elif nextMode == 'same':
                      silent = k.silentMode
                      k.setState(modeName, 1, handler=k.generalModeHandler)
                      self.reinitMode(modeName) # Re-enter this mode.
                      k.silentMode = silent
                  else:
                      k.silentMode = False # All silent modes must do --> set-silent-mode.
                      self.initMode(event, nextMode) # Enter another mode.
                      # Careful: k.initMode can execute commands that will destroy a commander.
                      # if g.app.quitting or not c.exists: return
      #@+node:ekr.20061031131434.181: *3* k.Shortcuts & bindings
      #@+node:ekr.20061031131434.176: *4* k.computeInverseBindingDict
      def computeInverseBindingDict(self):
          k = self
          d = {}
              # keys are minibuffer command names, values are shortcuts.
          for stroke in k.bindingsDict:
              assert g.isStroke(stroke), repr(stroke)
              aList = k.bindingsDict.get(stroke, [])
              for bi in aList:
                  shortcutList = k.bindingsDict.get(bi.commandName, [])
                      # Bug fix: 2017/03/26.
                  bi_list = k.bindingsDict.get(stroke, g.BindingInfo(kind='dummy', pane='all'))
                      # Important: only bi.pane is required below.
                  for bi in bi_list:
                      pane = '%s:' % (bi.pane)
                      data = (pane, stroke)
                      if data not in shortcutList:
                          shortcutList.append(data)
                  d[bi.commandName] = shortcutList
          return d
      #@+node:ekr.20061031131434.179: *4* k.getShortcutForCommand/Name
      def getStrokeForCommandName(self, commandName):
          k = self; c = k.c
          command = c.commandsDict.get(commandName)
          if command:
              for stroke, aList in k.bindingsDict.items():
                  for bi in aList:
                      if bi.commandName == commandName:
                          return stroke
          return None
      #@+node:ekr.20090518072506.8494: *4* k.isFKey
      def isFKey(self, stroke):
          # k = self
          if not stroke: return False
          assert g.isString(stroke) or g.isStroke(stroke)
          s = stroke.s if g.isStroke(stroke) else stroke
          s = s.lower()
          return s.startswith('f') and len(s) <= 3 and s[1:].isdigit()
      #@+node:ekr.20061031131434.182: *4* k.isPlainKey
      def isPlainKey(self, stroke):
          '''Return true if the shortcut refers to a plain (non-Alt,non-Ctl) key.'''
          if not stroke:
              return False
          if not g.isStroke(stroke):
              # Happens during unit tests.
              stroke = g.KeyStroke(stroke)
          #
          # altgr combos (Alt+Ctrl) are always plain keys
          # g.KeyStroke does not handle this, because it has no "c" ivar.
          #
          if stroke.isAltCtrl() and not self.enable_alt_ctrl_bindings:
              return True
          return stroke.isPlainKey()
      #@+node:ekr.20061031131434.191: *4* k.prettyPrintKey
      def prettyPrintKey(self, stroke, brief=False):
          
          if not stroke:
              return ''
          if not g.assert_is(stroke, g.KeyStroke):
              return stroke
          return stroke.prettyPrint()
      #@+node:ekr.20110609161752.16459: *4* k.setLossage
      def setLossage(self, ch, stroke):
  
          # k = self
          if ch or stroke:
              if len(g.app.lossage) > 99:
                  g.app.lossage.pop()
          # This looks like a memory leak, but isn't.
          g.app.lossage.insert(0, (ch, stroke),)
      #@+node:ekr.20110606004638.16929: *4* k.stroke2char
      def stroke2char(self, stroke):
          '''
          Convert a stroke to an (insertable) char.
          This method allows Leo to use strokes everywhere.
          '''
          if not stroke:
              return ''
          if not g.isStroke(stroke):
              # vim commands pass a plain key.
              stroke = g.KeyStroke(stroke)
          return stroke.toInsertableChar()
      #@+node:ekr.20061031131434.193: *3* k.States
      #@+node:ekr.20061031131434.194: *4* k.clearState
      def clearState(self):
          '''Clear the key handler state.'''
          k = self
          k.state.kind = None
          k.state.n = None
          k.state.handler = None
      #@+node:ekr.20061031131434.196: *4* k.getState
      def getState(self, kind):
          k = self
          val = k.state.n if k.state.kind == kind else 0
          return val
      #@+node:ekr.20061031131434.195: *4* k.getStateHandler
      def getStateHandler(self):
          return self.state.handler
      #@+node:ekr.20061031131434.197: *4* k.getStateKind
      def getStateKind(self):
          return self.state.kind
      #@+node:ekr.20061031131434.198: *4* k.inState
      def inState(self, kind=None):
          k = self
          if kind:
              return k.state.kind == kind and k.state.n is not None
          return k.state.kind and k.state.n is not None
      #@+node:ekr.20080511122507.4: *4* k.setDefaultInputState
      def setDefaultInputState(self):
          k = self; state = k.defaultUnboundKeyAction
          k.setInputState(state)
      #@+node:ekr.20110209093958.15411: *4* k.setEditingState
      def setEditingState(self):
          k = self; state = k.defaultEditingAction
          k.setInputState(state)
      #@+node:ekr.20061031131434.133: *4* k.setInputState
      def setInputState(self, state, set_border=False):
          k = self
          k.unboundKeyAction = state
      #@+node:ekr.20061031131434.199: *4* k.setState
      def setState(self, kind, n, handler=None):
  
          k = self
          if kind and n is not None:
              k.state.kind = kind
              k.state.n = n
              if handler:
                  k.state.handler = handler
          else:
              k.clearState()
          # k.showStateAndMode()
      #@+node:ekr.20061031131434.192: *4* k.showStateAndMode
      def showStateAndMode(self, w=None, prompt=None, setFocus=True):
          '''Show the state and mode at the start of the minibuffer.'''
          c, k = self.c, self
          state = k.unboundKeyAction
          mode = k.getStateKind()
          if not g.app.gui:
              return
          if not w:
              if hasattr(g.app.gui, 'set_minibuffer_label'):
                  pass # we don't need w
              else:
                  w = g.app.gui.get_focus(c)
                  if not w:
                      return
          isText = g.isTextWrapper(w)
          # This fixes a problem with the tk gui plugin.
          if mode and mode.lower().startswith('isearch'):
              return
          wname = g.app.gui.widget_name(w).lower()
          # Get the wrapper for the headline widget.
          if wname.startswith('head'):
              if hasattr(c.frame.tree, 'getWrapper'):
                  if hasattr(w, 'widget'): w2 = w.widget
                  else: w2 = w
                  w = c.frame.tree.getWrapper(w2, item=None)
                  isText = bool(w) # A benign hack.
          if mode:
              if mode in ('getArg', 'getFileName', 'full-command'):
                  s = None
              elif prompt:
                  s = prompt
              else:
                  mode = mode.strip()
                  if mode.endswith('-mode'):
                      mode = mode[: -5]
                  s = '%s Mode' % mode.capitalize()
          elif c.vim_mode and c.vimCommands:
              c.vimCommands.show_status()
              return
          else:
              s = '%s State' % state.capitalize()
              if c.editCommands.extendMode:
                  s = s + ' (Extend Mode)'
          if s:
              k.setLabelBlue(s)
          if w and isText:
              # k.showStateColors(inOutline,w)
              k.showStateCursor(state, w)
          # 2015/07/11: reset the status line.
          if hasattr(c.frame.tree, 'set_status_line'):
              c.frame.tree.set_status_line(c.p)
      #@+node:ekr.20110202111105.15439: *4* k.showStateCursor
      def showStateCursor(self, state, w):
          pass
      #@+node:ekr.20061031131434.200: *3* k.universalDispatcher & helpers
      def universalDispatcher(self, event):
          '''Handle accumulation of universal argument.'''
          #@+<< about repeat counts >>
          #@+node:ekr.20061031131434.201: *4* << about repeat counts >>
          #@@nocolor
          #@+at  Any Emacs command can be given a numeric argument. Some commands interpret the
          # argument as a repetition count. For example, giving an argument of ten to the
          # key C-f (the command forward-char, move forward one character) moves forward ten
          # characters. With these commands, no argument is equivalent to an argument of
          # one. Negative arguments are allowed. Often they tell a command to move or act
          # backwards.
          # 
          # If your keyboard has a META key, the easiest way to specify a numeric argument
          # is to type digits and/or a minus sign while holding down the the META key. For
          # example,
          # 
          # M-5 C-n
          # 
          # moves down five lines. The characters Meta-1, Meta-2, and so on, as well as
          # Meta--, do this because they are keys bound to commands (digit-argument and
          # negative-argument) that are defined to contribute to an argument for the next
          # command.
          # 
          # Another way of specifying an argument is to use the C-u (universal-argument)
          # command followed by the digits of the argument. With C-u, you can type the
          # argument digits without holding down shift keys. To type a negative argument,
          # start with a minus sign. Just a minus sign normally means -1. C-u works on all
          # terminals.
          # 
          # C-u followed by a character which is neither a digit nor a minus sign has the
          # special meaning of "multiply by four". It multiplies the argument for the next
          # command by four. C-u twice multiplies it by sixteen. Thus, C-u C-u C-f moves
          # forward sixteen characters. This is a good way to move forward "fast", since it
          # moves about 1/5 of a line in the usual size screen. Other useful combinations
          # are C-u C-n, C-u C-u C-n (move down a good fraction of a screen), C-u C-u C-o
          # (make "a lot" of blank lines), and C-u C-k (kill four lines).
          # 
          # Some commands care only about whether there is an argument and not about its
          # value. For example, the command M-q (fill-paragraph) with no argument fills
          # text; with an argument, it justifies the text as well. (See section Filling
          # Text, for more information on M-q.) Just C-u is a handy way of providing an
          # argument for such commands.
          # 
          # Some commands use the value of the argument as a repeat count, but do something
          # peculiar when there is no argument. For example, the command C-k (kill-line)
          # with argument n kills n lines, including their terminating newlines. But C-k
          # with no argument is special: it kills the text up to the next newline, or, if
          # point is right at the end of the line, it kills the newline itself. Thus, two
          # C-k commands with no arguments can kill a non-blank line, just like C-k with an
          # argument of one. (See section Deletion and Killing, for more information on
          # C-k.)
          # 
          # A few commands treat a plain C-u differently from an ordinary argument. A few
          # others may treat an argument of just a minus sign differently from an argument
          # of -1. These unusual cases will be described when they come up; they are always
          # to make the individual command more convenient to use.
          #@-<< about repeat counts >>
          k = self
          state = k.getState('u-arg')
          stroke = event.stroke if event else ''
          if state == 0:
              k.dispatchEvent = event
              # The call should set the label.
              k.setState('u-arg', 1, k.universalDispatcher)
              k.repeatCount = 1
          elif state == 1:
              char = event.char if event else ''
              if char == 'Escape':
                  k.keyboardQuit()
              elif char == k.universalArgKey:
                  k.repeatCount = k.repeatCount * 4
              elif char.isdigit() or char == '-':
                  k.updateLabel(event)
              elif char in (
                  'Alt_L', 'Alt_R',
                  'Control_L', 'Control_R',
                  'Meta_L', 'Meta_R',
                  'Shift_L', 'Shift_R',
              ):
                  k.updateLabel(event)
              else:
                  # *Anything* other than C-u, '-' or a numeral is taken to be a command.
                  val = k.getLabel(ignorePrompt=True)
                  try: n = int(val) * k.repeatCount
                  except ValueError: n = 1
                  k.clearState()
                  event = k.dispatchEvent
                  k.executeNTimes(event, n)
                  k.keyboardQuit()
          elif state == 2:
              k.doControlU(event, stroke)
      #@+node:ekr.20061031131434.202: *4* k.executeNTimes
      def executeNTimes(self, event, n):
  
          c, k = self.c, self
          w = event and event.widget
          stroke = event.stroke if event else ''
          if not stroke: return
          if stroke == k.fullCommandKey:
              for z in range(n):
                  k.fullCommand(event)
          else:
              bi = k.getPaneBinding(stroke, event and event.widget)
              if bi:
                  for z in range(n):
                      event = g.app.gui.create_key_event(c, event=event, w=w)
                      k.masterCommand(
                          commandName=None,
                          event=event,
                          func=bi.func,
                          stroke=stroke)
              else:
                  for z in range(n):
                      k.masterKeyHandler(event)
      #@+node:ekr.20061031131434.203: *4* doControlU
      def doControlU(self, event, stroke):
          k = self
          ch = event.char if event else ''
          k.setLabelBlue('Control-u %s' % g.stripBrackets(stroke))
          if ch == '(':
              k.clearState()
              k.resetLabel()
      #@-others
  #@+node:ekr.20120208064440.10150: ** class ModeInfo
  class ModeInfo:
  
      def __repr__(self):
          return '<ModeInfo %s>' % self.name
  
      __str__ = __repr__
      #@+others
      #@+node:ekr.20120208064440.10193: *3* mode_i. ctor
      def __init__(self, c, name, aList):
  
          self.c = c
          self.d = {} # The bindings in effect for this mode.
              # Keys are names of (valid) command names, values are BindingInfo objects.
          self.entryCommands = []
              # A list of BindingInfo objects.
          self.k = c.k
          self.name = self.computeModeName(name)
          self.prompt = self.computeModePrompt(self.name)
          self.init(name, aList)
      #@+node:ekr.20120208064440.10152: *3* mode_i.computeModeName
      def computeModeName(self, name):
          s = name.strip().lower()
          j = s.find(' ')
          if j > -1: s = s[: j]
          if s.endswith('mode'):
              s = s[: -4].strip()
          if s.endswith('-'):
              s = s[: -1]
          i = s.find('::')
          if i > -1:
              # The actual mode name is everything up to the "::"
              # The prompt is everything after the prompt.
              s = s[: i]
          return s + '-mode'
      #@+node:ekr.20120208064440.10156: *3* mode_i.computeModePrompt
      def computeModePrompt(self, name):
          assert name == self.name
          s = 'enter-' + name.replace(' ', '-')
          i = s.find('::')
          if i > -1:
              # The prompt is everything after the '::'
              prompt = s[i + 2:].strip()
          else:
              prompt = s
          return prompt
      #@+node:ekr.20120208064440.10160: *3* mode_i.createModeBindings
      def createModeBindings(self, w):
          '''Create mode bindings for w, a text widget.'''
          c, d, k, modeName = self.c, self.d, self.k, self.name
          for commandName in d:
              func = c.commandsDict.get(commandName)
              if not func:
                  g.es_print('no such command: %s Referenced from %s' % (
                      commandName, modeName))
                  continue
              aList = d.get(commandName, [])
              for bi in aList:
                  stroke = bi.stroke
                  # Important: bi.val is canonicalized.
                  if stroke and stroke not in ('None', 'none', None):
                      assert g.isStroke(stroke)
                      k.makeMasterGuiBinding(stroke)
                      # Create the entry for the mode in k.masterBindingsDict.
                      # Important: this is similar, but not the same as k.bindKeyToDict.
                      # Thus, we should **not** call k.bindKey here!
                      d2 = k.masterBindingsDict.get(modeName, {})
                      d2[stroke] = g.BindingInfo(
                          kind='mode<%s>' % (modeName), # 2012/01/23
                          commandName=commandName,
                          func=func,
                          nextMode=bi.nextMode,
                          stroke=stroke)
                      k.masterBindingsDict[modeName] = d2
      #@+node:ekr.20120208064440.10195: *3* mode_i.createModeCommand
      def createModeCommand(self):
          c = self.c
          key = 'enter-' + self.name.replace(' ', '-')
  
          def enterModeCallback(event=None, self=self):
              self.enterMode()
  
          c.commandsDict[key] = f = enterModeCallback
          g.trace('(ModeInfo)', f.__name__, key,
              'len(c.commandsDict.keys())', len(list(c.commandsDict.keys())))
      #@+node:ekr.20120208064440.10180: *3* mode_i.enterMode
      def enterMode(self):
  
          c, k = self.c, self.k
          c.inCommand = False
              # Allow inner commands in the mode.
          event = None
          k.generalModeHandler(event, modeName=self.name)
      #@+node:ekr.20120208064440.10153: *3* mode_i.init
      def init(self, name, dataList):
          '''aList is a list of tuples (commandName,bi).'''
          c, d, modeName = self.c, self.d, self.name
          for name, bi in dataList:
              if not name:
                  # An entry command: put it in the special *entry-commands* key.
                  self.entryCommands.append(bi)
              elif bi is not None:
                  # A regular shortcut.
                  bi.pane = modeName
                  aList = d.get(name, [])
                  # Important: use previous bindings if possible.
                  key2, aList2 = c.config.getShortcut(name)
                  aList3 = [z for z in aList2 if z.pane != modeName]
                  if aList3:
                      aList.extend(aList3)
                  aList.append(bi)
                  d[name] = aList
      #@+node:ekr.20120208064440.10158: *3* mode_i.initMode
      def initMode(self):
  
          c, k = self.c, self.c.k
          k.inputModeName = self.name
          k.silentMode = False
          for bi in self.entryCommands:
              commandName = bi.commandName
              k.simulateCommand(commandName)
              # Careful, the command can kill the commander.
              if g.app.quitting or not c.exists: return
              # New in Leo 4.5: a startup command can immediately transfer to another mode.
              if commandName.startswith('enter-'):
                  return
          # Create bindings after we know whether we are in silent mode.
          w = k.modeWidget if k.silentMode else k.w
          k.createModeBindings(self.name, self.d, w)
          k.showStateAndMode(prompt=self.name)
      #@-others
  #@-others
  #@@language python
  #@@tabwidth -4
  #@@pagewidth 70
  #@-leo
  