Source code for leo.core.leoKeys

# -*- 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
[docs]class AutoCompleterClass(object): '''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)
[docs] 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()
[docs] 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
[docs] @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
[docs] @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
[docs] @cmd('disable-autocompleter') def disableAutocompleter(self, event=None): '''Disable the autocompleter.''' self.k.enable_autocompleter = False self.showAutocompleterStatus()
[docs] @cmd('disable-calltips') def disableCalltips(self, event=None): '''Disable calltips.''' self.k.enable_calltips = False self.showCalltipsStatus()
[docs] @cmd('enable-autocompleter') def enableAutocompleter(self, event=None): '''Enable the autocompleter.''' self.k.enable_autocompleter = True self.showAutocompleterStatus()
[docs] @cmd('enable-calltips') def enableCalltips(self, event=None): '''Enable calltips.''' self.k.enable_calltips = True self.showCalltipsStatus()
[docs] @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()
[docs] @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
[docs] @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
[docs] @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
[docs] 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)
[docs] 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
[docs] 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
[docs] def appendTabName(self, word): self.setTabName(self.tabName + '.' + word)
[docs] def beginTabName(self, word): self.setTabName('AutoComplete ' + word)
[docs] def clearTabName(self): self.setTabName('AutoComplete ')
[docs] 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!
[docs] 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
[docs] 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
[docs] 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) else: if 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() return None else: self.abort() # 2011/06/17. return 'do-standard-keys'
#@+node:ekr.20061031131434.20: *4* ac.calltip & helpers
[docs] 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
[docs] def calltip_fail(self, prefix): '''Evaluation of prefix failed.''' self.insert_string('(')
#@+node:ekr.20110512090917.14469: *5* ac.calltip_success
[docs] 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
[docs] 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
[docs] 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
[docs] 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)
[docs] 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
[docs] 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
[docs] 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) 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 # Always try the Leo completions first. # Fall back to the codewise completions. aList = ( self.get_leo_completions(prefix) or self.get_codewise_completions(prefix) ) d[prefix] = aList return aList
#@+node:ekr.20110510120621.14539: *5* ac.get_codewise_completions & helpers
[docs] 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
[docs] 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)
[docs] 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
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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
[docs] 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.clock() 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.clock() # # 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.clock() except ValueError: t3 = time.clock() completions = None g.printObj(source_lines[n0-1:n0+30]) print('ERROR', p.h) if completions is None: 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] # tail = line[local_column:] # print('%s completions for %r' % (len(completions), head.strip())) # print(' get: %5.4f sec.' % (t2-t1)) # print('jedi: %5.4f sec.' % (t3-t2)) # print('n0: %s len(source): %s jedi_line: %s' % (n0, len(source), jedi_line)) # print('LINE: %r' % line) # print('HEAD: %r' % head) # print('TAIL: %r' % tail) return completions
#@+node:ekr.20180526211127.1: *6* ac.add_prefix
[docs] 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
[docs] 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
[docs] 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
[docs] 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 elif 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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: # 2011/06/17. # 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs]class ContextSniffer(object): """ 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
[docs] 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
[docs] 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
[docs] 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
[docs]class FileNameChooser(object): '''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
[docs] def compute_tab_list(self): '''Compute the list of completions.''' path = self.get_label() # Fix bug 215: insert-file-name doesn't process ~ # https://github.com/leo-editor/leo-editor/issues/215 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
[docs] 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
[docs] 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
[docs] 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)
[docs] 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
[docs] def extend_label(self, s): '''Extend the label by s.''' self.c.k.extendLabel(s, select=False, protect=False)
[docs] def get_label(self): '''Return the label, not including the prompt.''' return self.c.k.getLabel(ignorePrompt=True)
[docs] 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
[docs] 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
[docs]class GetArg(object): ''' 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())
[docs] 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 # note order. else: # 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())
[docs] 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: # Fix #323: https://github.com/leo-editor/leo-editor/issues/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.
[docs] 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 else: # Fix #323: https://github.com/leo-editor/leo-editor/issues/323 # A big simplifcation: always call ga.do_tab_list self.do_tab_cycling(common_prefix, tabList) c.minibufferWantsFocus()
#@+node:ekr.20140818145250.18235: *4* ga.do_tab_callback
[docs] 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 else: return False
#@+node:ekr.20140819050118.18317: *4* ga.do_tab_cycling
[docs] 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) # Fix #323: https://github.com/leo-editor/leo-editor/issues/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
[docs] 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)
[docs] def get_label(self): '''Return the label, not including the prompt.''' return self.c.k.getLabel(ignorePrompt=True)
[docs] 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
[docs] 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
[docs] def cancel_after_state(self): self.after_get_arg_state = None
#@+node:ekr.20140816165728.18955: *4* ga.do_char
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] def get_command(self, s): '''Return the command part of a minibuffer contents s.''' if s.startswith(':'): # A vim-like command. if len(s) == 1: return s elif s[1].isalpha(): command = [':'] for ch in s[1:]: if ch.isalnum() or ch == '-': command.append(ch) else: break return ''.join(command) elif s.startswith(':%s'): return s[: 3] else: # Special case for :! and :% etc. return s[: 2] else: command = [] for ch in s: if ch.isalnum() or ch in '@_-': command.append(ch) else: break return ''.join(command)
#@+node:ekr.20140818085719.18227: *3* ga.get_minibuffer_command_name
[docs] 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
[docs] def is_command(self, s): '''Return False if something, even a blank, follows a command.''' if s.startswith('@'): return True elif s.startswith(':'): if len(s) == 1: return True elif s[1].isalpha(): for ch in s[1:]: if not ch.isalnum() and ch != '-': return False return True else: # Special case for :! and :% etc. return len(s) == 2 else: for ch in s: if not ch.isalnum() and ch not in '_-': return False return True
#@+node:ekr.20140816165728.18959: *3* ga.show_tab_list & helper
[docs] 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
[docs] 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' elif fn2.endswith('leosettings.leo'): return 'G' else: return 'L' else: return '?' else: return ' '
#@-others #@+node:ekr.20061031131434.74: ** class KeyHandlerClass
[docs]class KeyHandlerClass(object): ''' 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.swap_mac_keys = False # How to init this?? 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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)
[docs] 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
[docs] 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
[docs] def oops(self): g.trace('Should be defined in subclass:', g.callers(4))
#@+node:ekr.20120217070122.10479: *4* k.reloadSettings
[docs] 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_caps_lock = getBool('ignore_caps_lock') 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.swap_mac_keys = getBool('swap_mac_keys') 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)
[docs] 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
[docs] 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
[docs] 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
[docs] 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 else: return True
#@+node:ekr.20120130074511.10227: *5* k.kill_one_shortcut
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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 != 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
[docs] 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 == 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
[docs] 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)
[docs] 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
[docs] 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
[docs] @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
[docs] 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 else: # 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
[docs] 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
[docs] def menuCommandKey(self, event=None): # This method must exist, but it never gets called. pass
#@+node:ekr.20061031131434.119: *4* k.printBindings & helper
[docs] @cmd('print-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
[docs] 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
[docs] @cmd('print-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
[docs] @cmd('print-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
[docs] @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
[docs] 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: return k.keyboardQuit()
#@+node:ekr.20061031131434.123: *4* k.set-xxx-State
[docs] @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()
[docs] @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()
[docs] @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
[docs] @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
[docs] 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
[docs] 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)
[docs] 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)
[docs] def doBackSpace(self, tabList, completion=True): '''Convenience method mapping k.doBackSpace to ga.do_back_space.''' self.getArgInstance.do_back_space(tabList, completion)
[docs] def doTabCompletion(self, tabList): '''Convenience method mapping k.doTabCompletion to ga.do_tab.''' self.getArgInstance.do_tab(tabList)
[docs] 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
[docs] @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
[docs] 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 assert g.isString(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) else: g.error(message)
#@+node:ekr.20071212104050: *4* k.overrideCommand
[docs] 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
[docs] 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
[docs] 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
[docs] 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 else: return None elif g.app.unitTesting: raise AttributeError else: g.error('simulateCommand: no command for %s' % (commandName)) return None
#@+node:ekr.20170324143353.1: *5* k.commandExists
[docs] 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 else: return None
#@+node:ekr.20140813052702.18203: *4* k.getFileName
[docs] 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
[docs] 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(event.char), 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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': k.getArg(event, stroke=stroke) return True 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 else: # 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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 = g.u('%s %s\n')%(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 elif p.h.startswith(('@command', '@button')): udata = c.undoer.beforeChangeNodeContents(p) cmd = p.h.split(g.u('@key'),1)[0] p.h = g.u('%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 else: # this should never happen g.error('not in settings node shortcut')
#@+node:vitalije.20170709151653.1: *6* k.isInShortcutBodyLine _cmd_handle_input_pattern = re.compile(g.u('[A-Za-z0-9_\\-]+\\s*='))
[docs] 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
[docs] 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
[docs] 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
[docs] 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': return True # # No binding exists. return False
#@+node:ekr.20180418114300.1: *6* k.handleMinibufferHelper
[docs] 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: c.minibufferWantsFocus() return 'found'
#@+node:ekr.20180418031118.1: *5* k.isSpecialKey
[docs] def isSpecialKey(self, event): '''Return True if char is a special key.''' if not event: # An empty event is not an error. return False return event.char in g.app.gui.ignoreChars
#@+node:ekr.20180418024449.1: *5* k.keyboardQuit
[docs] 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
[docs] 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
[docs] 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: return commandName = commandName or func and func.__name__ or '<no function>' 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
[docs] 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
[docs] 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
[docs] 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 else: 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
[docs] 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
[docs] 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
[docs] 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):] else: return s or ''
#@+node:ekr.20080408060320.791: *4* k.killLine
[docs] 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
[docs] def protectLabel(self): k = self; w = self.w if not w: return k.mb_prefix = w.getAllText()
#@+node:ekr.20061031170011.7: *4* k.resetLabel
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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)
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] @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)
[docs] @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
[docs] 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???)
[docs] 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)
[docs] 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 (changed) #@+node:ekr.20061031131434.163: *4* k.initMode (changed)
[docs] 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 else: k.modeBindingsDict = d bi = d.get('*command-prompt*') if bi: prompt = bi.kind # A kludge. else: prompt = 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)
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] def getStateHandler(self): return self.state.handler
#@+node:ekr.20061031131434.197: *4* k.getStateKind
[docs] def getStateKind(self): return self.state.kind
#@+node:ekr.20061031131434.198: *4* k.inState
[docs] def inState(self, kind=None): k = self if kind: return k.state.kind == kind and k.state.n is not None else: return k.state.kind and k.state.n is not None
#@+node:ekr.20080511122507.4: *4* k.setDefaultInputState
[docs] def setDefaultInputState(self): k = self; state = k.defaultUnboundKeyAction k.setInputState(state)
#@+node:ekr.20110209093958.15411: *4* k.setEditingState
[docs] def setEditingState(self): k = self; state = k.defaultEditingAction k.setInputState(state)
#@+node:ekr.20061031131434.133: *4* k.setInputState
[docs] def setInputState(self, state, set_border=False): k = self k.unboundKeyAction = state
#@+node:ekr.20061031131434.199: *4* k.setState
[docs] 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
[docs] 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
[docs] def showStateCursor(self, state, w): pass
#@+node:ekr.20061031131434.200: *3* k.universalDispatcher & helpers
[docs] 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
[docs] 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
[docs] 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
[docs]class ModeInfo(object): 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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
[docs] 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