Coverage for C:\leo.repo\leo-editor\leo\core\leoKeys.py : 28%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2#@+leo-ver=5-thin
3#@+node:ekr.20061031131434: * @file leoKeys.py
4#@@first
5"""Gui-independent keystroke handling for Leo."""
6# pylint: disable=eval-used
7# pylint: disable=deprecated-method
8#@+<< imports >>
9#@+node:ekr.20061031131434.1: ** << imports >> (leoKeys)
10import inspect
11import os
12import re
13import string
14import sys
15import textwrap
16import time
17from typing import Dict, List, Tuple
18from leo.core import leoGlobals as g
19from leo.commands import gotoCommands
20from leo.external import codewise
21try:
22 import jedi
23except ImportError:
24 jedi = None
25#@-<< imports >>
26#@+<< Key bindings, an overview >>
27#@+node:ekr.20130920121326.11281: ** << Key bindings, an overview >>
28#@@language rest
29#@+at
30# The big pictures of key bindings:
31#
32# 1. Code in leoKeys.py and in leoConfig.py converts user key settings to
33# various Python **binding dictionaries** defined in leoKeys.py.
34#
35# 2. An instance of LeoQtEventFilter should be attached to all visible panes
36# in Leo's main window. g.app.gui.setFilter does this.
37#
38# 3. LeoQtEventFilter.eventFilter calls k.masterKeyhandler for every
39# keystroke. eventFilter passes only just the event argument to
40# k.masterKeyHandler. The event arg gives both the widget in which the
41# event occurs and the keystroke.
42#
43# 4. k.masterKeyHandler and its helpers use the event argument and the
44# binding dictionaries to execute the Leo command (if any) associated with
45# the incoming keystroke.
46#
47# Important details:
48#
49# 1. g.app.gui.setFilter allows various traces and assertions to be made
50# uniformly. The obj argument to setFilter is a QWidget object; the w
51# argument to setFilter can be either the same as obj, or a Leo
52# wrapper class. **Important**: the types of obj and w are not
53# actually all that important, as discussed next.
54#
55# 2. The logic in k.masterKeyHandler and its helpers is long and involved:
56#
57# A. k.getPaneBinding associates a command with the incoming keystroke based
58# on a) the widget's name and b) whether the widget is a text widget
59# (which depends on the type of the widget).
60#
61# To do this, k.getPaneBinding uses a **binding priority table**. This
62# table is defined within k.getPaneBinding itself. The table indicates
63# which of several possible bindings should have priority. For instance,
64# if the widget is a text widget, a user binding for a 'text' widget takes
65# priority over a default key binding. Similarly, if the widget is Leo's
66# tree widget, a 'tree' binding has top priority. There are many other
67# details encapsulated in the table. The exactly details of the binding
68# priority table are open to debate, but in practice the resulting
69# bindings are as expeced.
70#
71# B. If k.getPaneBinding finds a command associated with the incoming
72# keystroke, k.masterKeyHandler executes the command.
73#
74# C. If k.getPaneBinding fails to bind the incoming keystroke to a command,
75# k.masterKeyHandler calls k.handleUnboundKeys to handle the keystroke.
76# Depending on the widget, and settings, and the keystroke,
77# k.handleUnboundKeys may do nothing, or it may call k.masterCommand to
78# insert a plain key into the widget.
79#@-<< Key bindings, an overview >>
80#@+<< about 'internal' bindings >>
81#@+node:ekr.20061031131434.2: ** << about 'internal' bindings >>
82#@@language rest
83#@+at
84# Here are the rules for translating key bindings (in leoSettings.leo)
85# into keys for k.bindingsDict:
86#
87# 1. The case of plain letters is significant: a is not A.
88#
89# 2. The Shift- prefix can be applied *only* to letters. Leo will ignore
90# (with a warning) the shift prefix applied to any other binding,
91# e.g., Ctrl-Shift-(
92#
93# 3. The case of letters prefixed by Ctrl-, Alt-, Key- or Shift- is
94# *not* significant. Thus, the Shift- prefix is required if you want
95# an upper-case letter (with the exception of 'bare' uppercase
96# letters.)
97#
98# The following table illustrates these rules. In each row, the first
99# entry is the key (for k.bindingsDict) and the other entries are
100# equivalents that the user may specify in leoSettings.leo:
101#
102# a, Key-a, Key-A
103# A, Shift-A
104# Alt-a, Alt-A
105# Alt-A, Alt-Shift-a, Alt-Shift-A
106# Ctrl-a, Ctrl-A
107# Ctrl-A, Ctrl-Shift-a, Ctrl-Shift-A
108# , Key-!,Key-exclam,exclam
109#
110# This table is consistent with how Leo already works (because it is
111# consistent with Tk's key-event specifiers). It is also, I think, the
112# least confusing set of rules.
113#@-<< about 'internal' bindings >>
114#@+<< about key dicts >>
115#@+node:ekr.20061031131434.3: ** << about key dicts >>
116#@@language rest
117#@+at
118# ivar Keys Values
119# ---- ---- ------
120# c.commandsDict command names (1) functions
121# k.bindingsDict shortcuts lists of BindingInfo objects
122# k.masterBindingsDict scope names (2) Interior masterBindingDicts (3)
123# k.masterGuiBindingsDict strokes list of widgets in which stoke is bound
124# inverseBindingDict (5) command names lists of tuples (pane,key)
125# modeCommandsDict (6) command name (7) inner modeCommandsDicts (8)
126#
127# New in Leo 4.7:
128# k.killedBindings is a list of command names for which bindings have been killed in local files.
129#
130# Notes:
131#
132# (1) Command names are minibuffer names (strings)
133# (2) Scope names are 'all','text',etc.
134# (3) Interior masterBindingDicts: Keys are strokes; values are BindingInfo objects.
135# (5) inverseBindingDict is **not** an ivar: it is computed by k.computeInverseBindingDict.
136# (6) A global dict: g.app.gui.modeCommandsDict
137# (7) enter-x-command
138# (8) Keys are command names, values are lists of BindingInfo objects.
139#@-<< about key dicts >>
140#@+others
141#@+node:ekr.20150509035140.1: ** ac_cmd (decorator)
142def ac_cmd(name):
143 """Command decorator for the AutoCompleter class."""
144 return g.new_cmd_decorator(name, ['c', 'k', 'autoCompleter'])
145#@+node:ekr.20150509035028.1: ** cmd (decorator)
146def cmd(name):
147 """Command decorator for the leoKeys class."""
148 return g.new_cmd_decorator(name, ['c', 'k',])
149#@+node:ekr.20061031131434.4: ** class AutoCompleterClass
150class AutoCompleterClass:
151 """A class that inserts autocompleted and calltip text in text widgets.
152 This class shows alternatives in the tabbed log pane.
154 The keyHandler class contains hooks to support these characters:
155 invoke-autocompleter-character (default binding is '.')
156 invoke-calltips-character (default binding is '(')
157 """
158 #@+others
159 #@+node:ekr.20061031131434.5: *3* ac.ctor & reloadSettings
160 def __init__(self, k):
161 """Ctor for AutoCompleterClass class."""
162 # Ivars...
163 self.c = k.c
164 self.k = k
165 self.language = None
166 # additional namespaces to search for objects, other code
167 # can append namespaces to this to extend scope of search
168 self.namespaces = []
169 self.qw = None # The object that supports qcompletion methods.
170 self.tabName = None # The name of the main completion tab.
171 self.verbose = False # True: print all members, regardless of how many there are.
172 self.w = None # The widget that gets focus after autocomplete is done.
173 self.warnings = {} # Keys are language names.
174 # Codewise pre-computes...
175 self.codewiseSelfList = [] # The (global) completions for "self."
176 self.completionsDict = {} # Keys are prefixes, values are completion lists.
177 self.reloadSettings()
179 def reloadSettings(self):
180 c = self.c
181 self.auto_tab = c.config.getBool('auto-tab-complete', True)
182 self.forbid_invalid = c.config.getBool('forbid-invalid-completions', False)
183 self.use_jedi = c.config.getBool('use-jedi', False)
184 # True: show results in autocompleter tab.
185 # False: show results in a QCompleter widget.
186 self.use_qcompleter = c.config.getBool('use-qcompleter', False)
187 #@+node:ekr.20061031131434.8: *3* ac.Top level
188 #@+node:ekr.20061031131434.9: *4* ac.autoComplete
189 @ac_cmd('auto-complete')
190 def autoComplete(self, event=None):
191 """An event handler for autocompletion."""
192 c, k = self.c, self.k
193 # pylint: disable=consider-using-ternary
194 w = event and event.w or c.get_focus()
195 if k.unboundKeyAction not in ('insert', 'overwrite'):
196 return
197 c.insertCharFromEvent(event)
198 if c.exists:
199 c.frame.updateStatusLine()
200 # Allow autocompletion only in the body pane.
201 if not c.widget_name(w).lower().startswith('body'):
202 return
203 self.language = g.scanForAtLanguage(c, c.p)
204 if w and k.enable_autocompleter:
205 self.w = w
206 self.start(event)
207 #@+node:ekr.20061031131434.10: *4* ac.autoCompleteForce
208 @ac_cmd('auto-complete-force')
209 def autoCompleteForce(self, event=None):
210 """Show autocompletion, even if autocompletion is not presently enabled."""
211 c, k = self.c, self.k
212 # pylint: disable=consider-using-ternary
213 w = event and event.w or c.get_focus()
214 if k.unboundKeyAction not in ('insert', 'overwrite'):
215 return
216 if c.exists:
217 c.frame.updateStatusLine()
218 # Allow autocompletion only in the body pane.
219 if not c.widget_name(w).lower().startswith('body'):
220 return
221 self.language = g.scanForAtLanguage(c, c.p)
222 if w:
223 self.w = w
224 self.start(event)
226 #@+node:ekr.20061031131434.12: *4* ac.enable/disable/toggleAutocompleter/Calltips
227 @ac_cmd('disable-autocompleter')
228 def disableAutocompleter(self, event=None):
229 """Disable the autocompleter."""
230 self.k.enable_autocompleter = False
231 self.showAutocompleterStatus()
233 @ac_cmd('disable-calltips')
234 def disableCalltips(self, event=None):
235 """Disable calltips."""
236 self.k.enable_calltips = False
237 self.showCalltipsStatus()
239 @ac_cmd('enable-autocompleter')
240 def enableAutocompleter(self, event=None):
241 """Enable the autocompleter."""
242 self.k.enable_autocompleter = True
243 self.showAutocompleterStatus()
245 @ac_cmd('enable-calltips')
246 def enableCalltips(self, event=None):
247 """Enable calltips."""
248 self.k.enable_calltips = True
249 self.showCalltipsStatus()
251 @ac_cmd('toggle-autocompleter')
252 def toggleAutocompleter(self, event=None):
253 """Toggle whether the autocompleter is enabled."""
254 self.k.enable_autocompleter = not self.k.enable_autocompleter
255 self.showAutocompleterStatus()
257 @ac_cmd('toggle-calltips')
258 def toggleCalltips(self, event=None):
259 """Toggle whether calltips are enabled."""
260 self.k.enable_calltips = not self.k.enable_calltips
261 self.showCalltipsStatus()
262 #@+node:ekr.20061031131434.13: *4* ac.showCalltips
263 @ac_cmd('show-calltips')
264 def showCalltips(self, event=None):
265 """Show the calltips at the cursor."""
266 c, k, w = self.c, self.c.k, event and event.w
267 if not w:
268 return
269 is_headline = c.widget_name(w).startswith('head')
270 if k.enable_calltips and not is_headline:
271 self.w = w
272 self.calltip()
273 else:
274 c.insertCharFromEvent(event)
275 #@+node:ekr.20061031131434.14: *4* ac.showCalltipsForce
276 @ac_cmd('show-calltips-force')
277 def showCalltipsForce(self, event=None):
278 """Show the calltips at the cursor, even if calltips are not presently enabled."""
279 c, w = self.c, event and event.w
280 if not w:
281 return
282 is_headline = c.widget_name(w).startswith('head')
283 if not is_headline:
284 self.w = w
285 self.calltip()
286 else:
287 c.insertCharFromEvent(event)
288 #@+node:ekr.20061031131434.15: *4* ac.showAutocompleter/CalltipsStatus
289 def showAutocompleterStatus(self):
290 """Show the autocompleter status."""
291 k = self.k
292 if not g.unitTesting:
293 s = f"autocompleter {'On' if k.enable_autocompleter else 'Off'}"
294 g.red(s)
296 def showCalltipsStatus(self):
297 """Show the autocompleter status."""
298 k = self.k
299 if not g.unitTesting:
300 s = f"calltips {'On'}" if k.enable_calltips else 'Off'
301 g.red(s)
302 #@+node:ekr.20061031131434.16: *3* ac.Helpers
303 #@+node:ekr.20110512212836.14469: *4* ac.exit
304 def exit(self):
306 trace = all(z in g.app.debug for z in ('abbrev', 'verbose'))
307 if trace:
308 g.trace('(AutoCompleterClass)')
309 c, p, u = self.c, self.c.p, self.c.undoer
310 w = self.w or c.frame.body.wrapper
311 c.k.keyboardQuit()
312 if self.use_qcompleter:
313 if self.qw:
314 self.qw.end_completer()
315 self.qw = None # Bug fix: 2013/09/24.
316 else:
317 for name in (self.tabName, 'Modules', 'Info'):
318 c.frame.log.deleteTab(name)
319 # Restore the selection range that may have been destroyed by changing tabs.
320 c.widgetWantsFocusNow(w)
321 i, j = w.getSelectionRange()
322 w.setSelectionRange(i, j, insert=j)
323 newText = w.getAllText()
324 if p.b == newText:
325 return
326 bunch = u.beforeChangeBody(p)
327 p.v.b = newText # p.b would cause a redraw.
328 u.afterChangeBody(p, 'auto-completer', bunch)
330 finish = exit
331 abort = exit
332 #@+node:ekr.20061031131434.18: *4* ac.append/begin/popTabName
333 def appendTabName(self, word):
334 self.setTabName(self.tabName + '.' + word)
336 def beginTabName(self, word):
337 self.setTabName('AutoComplete ' + word)
339 def clearTabName(self):
340 self.setTabName('AutoComplete ')
342 def popTabName(self):
343 s = self.tabName
344 i = s.rfind('.', 0, -1)
345 if i > -1:
346 self.setTabName(s[0:i])
348 # Underscores are not valid in Pmw tab names!
350 def setTabName(self, s):
351 c = self.c
352 if self.tabName:
353 c.frame.log.deleteTab(self.tabName)
354 self.tabName = s.replace('_', '') or ''
355 c.frame.log.clearTab(self.tabName)
356 #@+node:ekr.20110509064011.14556: *4* ac.attr_matches
357 def attr_matches(self, s, namespace):
358 """Compute matches when string s is of the form name.name....name.
360 Evaluates s using eval(s,namespace)
362 Assuming the text is of the form NAME.NAME....[NAME], and is evaluatable in
363 the namespace, it will be evaluated and its attributes (as revealed by
364 dir()) are used as possible completions.
366 For class instances, class members are are also considered.)
368 **Warning**: this can still invoke arbitrary C code, if an object
369 with a __getattr__ hook is evaluated.
371 """
372 # Seems to work great. Catches things like ''.<tab>
373 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", s)
374 if not m:
375 return []
376 expr, attr = m.group(1, 3)
377 try:
378 safe_expr = self.strip_brackets(expr)
379 obj = eval(safe_expr, namespace)
380 except Exception:
381 return []
382 # Build the result.
383 words = dir(obj)
384 n = len(attr)
385 result = [f"{expr}.{w}" for w in words if w[:n] == attr]
386 return result
387 #@+node:ekr.20061031131434.11: *4* ac.auto_completer_state_handler
388 def auto_completer_state_handler(self, event):
389 """Handle all keys while autocompleting."""
390 c, k, tag = self.c, self.k, 'auto-complete'
391 state = k.getState(tag)
392 ch = event.char if event else ''
393 stroke = event.stroke if event else ''
394 is_plain = k.isPlainKey(stroke)
395 if state == 0:
396 c.frame.log.clearTab(self.tabName)
397 common_prefix, prefix, tabList = self.compute_completion_list()
398 if tabList:
399 k.setState(tag, 1, handler=self.auto_completer_state_handler)
400 else:
401 self.exit()
402 elif ch in ('\n', 'Return'):
403 self.exit()
404 elif ch == 'Escape':
405 self.exit()
406 elif ch in ('\t', 'Tab'):
407 self.compute_completion_list()
408 elif ch in ('\b', 'BackSpace'):
409 self.do_backspace()
410 elif ch == '.':
411 self.insert_string('.')
412 self.compute_completion_list()
413 elif ch == '?':
414 self.info()
415 elif ch == '!':
416 # Toggle between verbose and brief listing.
417 self.verbose = not self.verbose
418 kind = 'ON' if self.verbose else 'OFF'
419 message = f"verbose completions {kind}"
420 g.es_print(message)
421 # This doesn't work because compute_completion_list clears the autocomplete tab.
422 # self.put('', message, tabName=self.tabName)
423 # This is almost invisible: the fg='red' is not honored.
424 c.frame.putStatusLine(message, fg='red')
425 self.compute_completion_list()
426 # elif ch == 'Down' and hasattr(self,'onDown'):
427 # self.onDown()
428 # elif ch == 'Up' and hasattr(self,'onUp'):
429 # self.onUp()
430 elif is_plain and ch and ch in string.printable:
431 self.insert_general_char(ch)
432 elif stroke == k.autoCompleteForceKey:
433 # This is probably redundant because completions will exist.
434 # However, it doesn't hurt, and it may be useful rarely.
435 common_prefix, prefix, tabList = self.compute_completion_list()
436 if tabList:
437 self.show_completion_list(common_prefix, prefix, tabList)
438 else:
439 g.warning('No completions')
440 self.exit()
441 else:
442 self.abort()
443 return 'do-standard-keys'
444 return None
445 #@+node:ekr.20061031131434.20: *4* ac.calltip & helpers
446 def calltip(self):
447 """Show the calltips for the present prefix.
448 ch is '(' if the user has just typed it.
449 """
450 obj, prefix = self.get_object()
451 if obj:
452 self.calltip_success(prefix, obj)
453 else:
454 self.calltip_fail(prefix)
455 self.exit()
456 #@+node:ekr.20110512090917.14468: *5* ac.calltip_fail
457 def calltip_fail(self, prefix):
458 """Evaluation of prefix failed."""
459 self.insert_string('(')
460 #@+node:ekr.20110512090917.14469: *5* ac.calltip_success
461 def calltip_success(self, prefix, obj):
462 try:
463 # Get the parenthesized argument list.
464 s1, s2, s3, s4 = inspect.getargspec(obj)
465 s = inspect.formatargspec(s1, s2, s3, s4)
466 except Exception:
467 self.insert_string('(')
468 return
469 # Clean s and insert it: don't include the opening "(".
470 if g.match(s, 1, 'self,'):
471 s = s[6:].strip()
472 elif g.match_word(s, 1, 'self'):
473 s = s[5:].strip()
474 else:
475 s = s[1:].strip()
476 self.insert_string("(", select=False)
477 self.insert_string(s, select=True)
478 #@+node:ekr.20061031131434.28: *4* ac.compute_completion_list & helper
479 def compute_completion_list(self):
480 """Return the autocompleter completion list."""
481 prefix = self.get_autocompleter_prefix()
482 key, options = self.get_cached_options(prefix)
483 if not options:
484 options = self.get_completions(prefix)
485 tabList, common_prefix = g.itemsMatchingPrefixInList(
486 prefix, options, matchEmptyPrefix=False)
487 if not common_prefix:
488 tabList, common_prefix = g.itemsMatchingPrefixInList(
489 prefix, options, matchEmptyPrefix=True)
490 if tabList:
491 self.show_completion_list(common_prefix, prefix, tabList)
492 return common_prefix, prefix, tabList
493 #@+node:ekr.20110514051607.14524: *5* ac.get_cached_options
494 def get_cached_options(self, prefix):
495 d = self.completionsDict
496 # Search the completions Dict for shorter and shorter prefixes.
497 i = len(prefix)
498 while i > 0:
499 key = prefix[:i]
500 i -= 1
501 # Make sure we report hits only of real objects.
502 if key.endswith('.'):
503 return key, []
504 options = d.get(key)
505 if options:
506 return key, options
507 return None, []
508 #@+node:ekr.20061031131434.29: *4* ac.do_backspace
509 def do_backspace(self):
510 """Delete the character and recompute the completion list."""
511 c, w = self.c, self.w
512 c.bodyWantsFocusNow()
513 i = w.getInsertPoint()
514 if i <= 0:
515 self.exit()
516 return
517 w.delete(i - 1, i)
518 w.setInsertPoint(i - 1)
519 if i <= 1:
520 self.exit()
521 else:
522 # Update the list. Abort if there is no prefix.
523 common_prefix, prefix, tabList = self.compute_completion_list()
524 if not prefix:
525 self.exit()
526 #@+node:ekr.20110510133719.14548: *4* ac.do_qcompleter_tab (not used)
527 def do_qcompleter_tab(self, prefix, options):
528 """Return the longest common prefix of all the options."""
529 matches, common_prefix = g.itemsMatchingPrefixInList(
530 prefix, options, matchEmptyPrefix=False)
531 return common_prefix
532 #@+node:ekr.20110509064011.14561: *4* ac.get_autocompleter_prefix
533 def get_autocompleter_prefix(self):
534 # Only the body pane supports auto-completion.
535 w = self.c.frame.body.wrapper
536 s = w.getAllText()
537 if not s:
538 return ''
539 i = w.getInsertPoint() - 1
540 i = j = max(0, i)
541 while i >= 0 and (s[i].isalnum() or s[i] in '._'):
542 i -= 1
543 i += 1
544 j += 1
545 prefix = s[i:j]
546 return prefix
547 #@+node:ekr.20110512212836.14471: *4* ac.get_completions & helpers
548 jedi_warning = False
550 def get_completions(self, prefix):
551 """Return jedi or codewise completions."""
552 d = self.completionsDict
553 if self.use_jedi:
554 try:
555 import jedi
556 except ImportError:
557 jedi = None
558 if not self.jedi_warning:
559 self.jedi_warning = True
560 g.es_print('can not import jedi')
561 g.es_print('ignoring @bool use_jedi = True')
562 if jedi:
563 aList = (
564 self.get_jedi_completions(prefix) or
565 # Prefer the jedi completions.
566 self.get_leo_completions(prefix))
567 d[prefix] = aList
568 return aList
569 #
570 # Not jedi. Use codewise.
571 # Precompute the codewise completions for '.self'.
572 if not self.codewiseSelfList:
573 aList = self.get_codewise_completions('self.')
574 self.codewiseSelfList = [z[5:] for z in aList]
575 d['self.'] = self.codewiseSelfList
576 # Use the cached list if it exists.
577 aList = d.get(prefix)
578 if aList:
579 return aList
580 aList = (
581 self.get_leo_completions(prefix) or
582 # Prefer the Leo completions.
583 self.get_codewise_completions(prefix)
584 )
585 d[prefix] = aList
586 return aList
587 #@+node:ekr.20110510120621.14539: *5* ac.get_codewise_completions & helpers
588 def get_codewise_completions(self, prefix):
589 """Use codewise to generate a list of hits."""
590 c = self.c
591 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", prefix)
592 if m:
593 varname = m.group(1)
594 ivar = m.group(3)
595 kind, aList = self.guess_class(c, varname)
596 else:
597 kind, aList = 'none', []
598 varname, ivar = None, None
599 if aList:
600 if kind == 'class':
601 hits = self.lookup_methods(aList, ivar)
602 hits.extend(self.codewiseSelfList)
603 elif kind == 'module':
604 hits = self.lookup_modules(aList, ivar)
605 else:
606 aList2 = prefix.split('.')
607 if aList2:
608 func = aList2[-1]
609 hits = self.lookup_functions(func)
610 else:
611 hits = []
612 if 1: # A kludge: add the prefix to each hit.
613 hits = [f"{varname}.{z}" for z in hits]
614 return hits
615 #@+node:ekr.20110510120621.14540: *6* ac.clean
616 def clean(self, hits):
617 """Clean up hits, a list of ctags patterns, for use in completion lists."""
618 # Just take the function name: ignore the signature & file.
619 aList = list(set([z[0] for z in hits]))
620 aList.sort()
621 return aList
622 #@+node:ekr.20110512232915.14481: *6* ac.clean_for_display (not used)
623 def clean_for_display(self, hits):
624 """Clean up hits, a list of ctags patterns, for display purposes."""
625 aList = []
626 for h in hits:
627 s = h[0]
628 # Display oriented: no good for completion list.
629 fn = h[1].strip()
630 if fn.startswith('/'):
631 sig = fn[2:-4].strip()
632 else:
633 sig = fn
634 aList.append(f"{s}: {sig}")
635 aList = list(set(aList))
636 aList.sort()
637 return aList
638 #@+node:ekr.20110510120621.14542: *6* ac.guess_class
639 def guess_class(self, c, varname) -> Tuple[str, List[str]]:
640 """Return kind, class_list"""
641 # if varname == 'g':
642 # return 'module',['leoGlobals']
643 if varname == 'p':
644 return 'class', ['position']
645 if varname == 'c':
646 return 'class', ['Commands']
647 if varname == 'self':
648 # Return the nearest enclosing class.
649 for p in c.p.parents():
650 h = p.h
651 m = re.search(r'class\s+(\w+)', h)
652 if m:
653 return 'class', [m.group(1)]
654 # This is not needed now that we add the completions for 'self'.
655 # aList = ContextSniffer().get_classes(c.p.b, varname)
656 return 'class', []
657 #@+node:ekr.20110510120621.14543: *6* ac.lookup_functions/methods/modules
658 def lookup_functions(self, prefix):
659 aList = codewise.cmd_functions([prefix])
660 hits = [z.split(None, 1) for z in aList if z.strip()]
661 return self.clean(hits)
663 def lookup_methods(self, aList, prefix): # prefix not used, only aList[0] used.
664 aList = codewise.cmd_members([aList[0]])
665 hits = [z.split(None, 1) for z in aList if z.strip()]
666 return self.clean(hits)
668 def lookup_modules(self, aList, prefix): # prefix not used, only aList[0] used.
669 aList = codewise.cmd_functions([aList[0]])
670 hits = [z.split(None, 1) for z in aList if z.strip()]
671 return self.clean(hits)
672 #@+node:ekr.20180519111302.1: *5* ac.get_jedi_completions & helper
673 def get_jedi_completions(self, prefix):
675 c = self.c
676 w = c.frame.body.wrapper
677 i = w.getInsertPoint()
678 p = c.p
679 body_s = p.b
680 #
681 # Get the entire source for jedi.
682 t1 = time.process_time()
683 goto = gotoCommands.GoToCommands(c)
684 root, fileName = goto.find_root(p)
685 if root:
686 source = goto.get_external_file_with_sentinels(root=root or p)
687 n0 = goto.find_node_start(p=p, s=source)
688 if n0 is None:
689 n0 = 0
690 else:
691 source = body_s
692 n0 = 0
693 t2 = time.process_time()
694 #
695 # Get local line
696 lines = g.splitLines(body_s)
697 row, column = g.convertPythonIndexToRowCol(body_s, i)
698 if row >= len(lines): # 2020/11/27
699 return []
700 line = lines[row]
701 #
702 # Find the global line, and compute offsets.
703 source_lines = g.splitLines(source)
704 for jedi_line, g_line in enumerate(source_lines[n0:]):
705 if line.lstrip() == g_line.lstrip():
706 # Adjust the column.
707 indent1 = len(line) - len(line.lstrip())
708 indent2 = len(g_line) - len(g_line.lstrip())
709 if indent2 >= indent1:
710 local_column = column # For traces.
711 column += abs(indent2 - indent1)
712 break
713 else:
714 completions = None
715 jedi_line, indent1, indent2 = None, None, None
716 if 0: # This *can* happen.
717 g.printObj(source_lines[n0 - 1 : n0 + 30])
718 print(f"can not happen: not found: {line!r}")
719 #
720 # Get the jedi completions.
721 if jedi and jedi_line is not None:
722 try:
723 # https://jedi.readthedocs.io/en/latest/docs/api.html#script
724 script = jedi.Script(source, path=g.shortFileName(fileName))
725 completions = script.complete(
726 line=1 + n0 + jedi_line,
727 column=column,
728 )
729 t3 = time.process_time()
730 except Exception:
731 t3 = time.process_time()
732 completions = None
733 g.printObj(source_lines[n0 - 1 : n0 + 30])
734 print('ERROR', p.h)
735 if not completions:
736 return []
737 # May be used in traces below.
738 assert t3 >= t2 >= t1
739 assert local_column is not None
740 completions = [z.name for z in completions]
741 completions = [self.add_prefix(prefix, z) for z in completions]
742 # Retain these for now...
743 # g.printObj(completions[:5])
744 # head = line[:local_column]
745 # ch = line[local_column:local_column+1]
746 # g.trace(len(completions), repr(ch), head.strip())
747 return completions
748 #@+node:ekr.20180526211127.1: *6* ac.add_prefix
749 def add_prefix(self, prefix, s):
750 """A hack to match the callers expectations."""
751 if prefix.find('.') > -1:
752 aList = prefix.split('.')
753 prefix = '.'.join(aList[:-1]) + '.'
754 return s if s.startswith(prefix) else prefix + s
755 #@+node:ekr.20110509064011.14557: *5* ac.get_leo_completions
756 def get_leo_completions(self, prefix):
757 """Return completions in an environment defining c, g and p."""
758 aList = []
759 for d in self.namespaces + [self.get_leo_namespace(prefix)]:
760 aList.extend(self.attr_matches(prefix, d))
761 aList.sort()
762 return aList
763 #@+node:ekr.20110512090917.14466: *4* ac.get_leo_namespace
764 def get_leo_namespace(self, prefix):
765 """
766 Return an environment in which to evaluate prefix.
767 Add some common standard library modules as needed.
768 """
769 k = self.k
770 d = {'c': k.c, 'p': k.c.p, 'g': g}
771 aList = prefix.split('.')
772 if len(aList) > 1:
773 name = aList[0]
774 m = sys.modules.get(name)
775 if m:
776 d[name] = m
777 return d
778 #@+node:ekr.20110512170111.14472: *4* ac.get_object
779 def get_object(self):
780 """Return the object corresponding to the current prefix."""
781 common_prefix, prefix1, aList = self.compute_completion_list()
782 if not aList:
783 return None, prefix1
784 if len(aList) == 1:
785 prefix = aList[0]
786 else:
787 prefix = common_prefix
788 if prefix.endswith('.') and self.use_qcompleter:
789 prefix += self.qcompleter.get_selection()
790 safe_prefix = self.strip_brackets(prefix)
791 for d in self.namespaces + [self.get_leo_namespace(prefix)]:
792 try:
793 obj = eval(safe_prefix, d)
794 break # only reached if none of the exceptions below occur
795 except AttributeError:
796 obj = None
797 except NameError:
798 obj = None
799 except SyntaxError:
800 obj = None
801 except Exception:
802 g.es_exception()
803 obj = None
804 return obj, prefix
805 #@+node:ekr.20061031131434.38: *4* ac.info
806 def info(self):
807 """Show the docstring for the present completion."""
808 c = self.c
809 obj, prefix = self.get_object()
810 c.frame.log.clearTab('Info', wrap='word')
811 put = lambda s: self.put('', s, tabName='Info')
812 put(prefix)
813 try:
814 argspec = inspect.getargspec(obj)
815 # uses None instead of empty list
816 argn = len(argspec.args or [])
817 defn = len(argspec.defaults or [])
818 put("args:")
819 simple_args = argspec.args[: argn - defn]
820 if not simple_args:
821 put(' (none)')
822 else:
823 put(' ' + ', '.join(' ' + i for i in simple_args))
824 put("keyword args:")
825 if not argspec.defaults:
826 put(' (none)')
827 for i in range(defn):
828 arg = argspec.args[-defn + i]
829 put(f" {arg} = {repr(argspec.defaults[i])}")
830 if argspec.varargs:
831 put("varargs: *" + argspec.varargs)
832 if argspec.keywords:
833 put("keywords: **" + argspec.keywords)
834 put('\n') # separate docstring
835 except TypeError:
836 put('\n') # not a callable
837 doc = inspect.getdoc(obj)
838 put(doc if doc else "No docstring for " + repr(prefix))
839 #@+node:ekr.20110510071925.14586: *4* ac.init_qcompleter
840 def init_qcompleter(self, event=None):
842 # Compute the prefix and the list of options.
843 prefix = self.get_autocompleter_prefix()
844 options = self.get_completions(prefix)
845 w = self.c.frame.body.wrapper.widget # A LeoQTextBrowser. May be none for unit tests.
846 if w and options:
847 self.qw = w
848 self.qcompleter = w.init_completer(options)
849 self.auto_completer_state_handler(event)
850 else:
851 if not g.unitTesting:
852 g.warning('No completions')
853 self.exit()
854 #@+node:ekr.20110511133940.14552: *4* ac.init_tabcompleter
855 def init_tabcompleter(self, event=None):
856 # Compute the prefix and the list of options.
857 prefix = self.get_autocompleter_prefix()
858 options = self.get_completions(prefix)
859 if options:
860 self.clearTabName() # Creates the tabbed pane.
861 self.auto_completer_state_handler(event)
862 else:
863 g.warning('No completions')
864 self.exit()
865 #@+node:ekr.20061031131434.39: *4* ac.insert_general_char
866 def insert_general_char(self, ch):
868 trace = all(z in g.app.debug for z in ('abbrev', 'verbose'))
869 k, w = self.k, self.w
870 if g.isWordChar(ch):
871 if trace:
872 g.trace('ch', repr(ch))
873 self.insert_string(ch)
874 common_prefix, prefix, aList = self.compute_completion_list()
875 if not aList:
876 if self.forbid_invalid:
877 # Delete the character we just inserted.
878 self.do_backspace()
879 # @bool auto_tab_complete is deprecated.
880 # Auto-completion makes no sense if it is False.
881 elif self.auto_tab and len(common_prefix) > len(prefix):
882 extend = common_prefix[len(prefix) :]
883 ins = w.getInsertPoint()
884 if trace:
885 g.trace('extend', repr(extend))
886 w.insert(ins, extend)
887 return
888 if ch == '(' and k.enable_calltips:
889 # This calls self.exit if the '(' is valid.
890 self.calltip()
891 else:
892 if trace:
893 g.trace('ch', repr(ch))
894 self.insert_string(ch)
895 self.exit()
896 #@+node:ekr.20061031131434.31: *4* ac.insert_string
897 def insert_string(self, s, select=False):
898 """
899 Insert an auto-completion string s at the insertion point.
901 Leo 6.4. This *part* of auto-completion is no longer undoable.
902 """
903 c, w = self.c, self.w
904 if not g.isTextWrapper(w):
905 return
906 c.widgetWantsFocusNow(w)
907 #
908 # Don't make this undoable.
909 # oldText = w.getAllText()
910 # oldSel = w.getSelectionRange()
911 # bunch = u.beforeChangeBody(p)
912 i = w.getInsertPoint()
913 w.insert(i, s)
914 if select:
915 j = i + len(s)
916 w.setSelectionRange(i, j, insert=j)
917 #
918 # Don't make this undoable.
919 # if 0:
920 # u.doTyping(p, 'Typing',
921 # oldSel=oldSel,
922 # oldText=oldText,
923 # newText=w.getAllText(),
924 # newInsert=w.getInsertPoint(),
925 # newSel=w.getSelectionRange())
926 # else:
927 # u.afterChangeBody(p, 'auto-complete', bunch)
928 if self.use_qcompleter and self.qw:
929 c.widgetWantsFocusNow(self.qw.leo_qc)
930 #@+node:ekr.20110314115639.14269: *4* ac.is_leo_source_file
931 def is_leo_source_file(self):
932 """Return True if this is one of Leo's source files."""
933 c = self.c
934 table = (z.lower() for z in (
935 'leoDocs.leo',
936 'LeoGui.leo', 'LeoGuiPluginsRef.leo',
937 # 'leoPlugins.leo', 'leoPluginsRef.leo',
938 'leoPy.leo', 'leoPyRef.leo',
939 'myLeoSettings.leo', 'leoSettings.leo',
940 'ekr.leo',
941 # 'test.leo',
942 ))
943 return c.shortFileName().lower() in table
944 #@+node:ekr.20101101175644.5891: *4* ac.put
945 def put(self, *args, **keys):
946 """Put s to the given tab.
948 May be overridden in subclasses."""
949 # print('autoCompleter.put',args,keys)
950 if g.unitTesting:
951 pass
952 else:
953 g.es(*args, **keys)
954 #@+node:ekr.20110511133940.14561: *4* ac.show_completion_list & helpers
955 def show_completion_list(self, common_prefix, prefix, tabList):
957 c = self.c
958 aList = common_prefix.split('.')
959 header = '.'.join(aList[:-1])
960 # "!" toggles self.verbose.
961 if self.verbose or self.use_qcompleter or len(tabList) < 20:
962 tabList = self.clean_completion_list(header, tabList,)
963 else:
964 tabList = self.get_summary_list(header, tabList)
965 if self.use_qcompleter:
966 # Put the completions in the QListView.
967 if self.qw:
968 self.qw.show_completions(tabList)
969 else:
970 # Update the tab name, creating the tab if necessary.
971 c.widgetWantsFocus(self.w)
972 c.frame.log.clearTab(self.tabName)
973 self.beginTabName(header + '.' if header else '')
974 s = '\n'.join(tabList)
975 self.put('', s, tabName=self.tabName)
976 #@+node:ekr.20110513104728.14453: *5* ac.clean_completion_list
977 def clean_completion_list(self, header, tabList):
978 """Return aList with header removed from the start of each list item."""
979 return [
980 z[len(header) + 1 :] if z.startswith(header) else z
981 for z in tabList]
982 #@+node:ekr.20110513104728.14454: *5* ac.get_summary_list
983 def get_summary_list(self, header, tabList):
984 """Show the possible starting letters,
985 but only if there are more than one.
986 """
987 d: Dict[str, int] = {}
988 for z in tabList:
989 tail = z[len(header) :] if z else ''
990 if tail.startswith('.'):
991 tail = tail[1:]
992 ch = tail[0] if tail else ''
993 if ch:
994 n = d.get(ch, 0)
995 d[ch] = n + 1
996 aList = [f"{ch2} {d.get(ch2)}" for ch2 in sorted(d)]
997 if len(aList) > 1:
998 tabList = aList
999 else:
1000 tabList = self.clean_completion_list(header, tabList)
1001 return tabList
1002 #@+node:ekr.20061031131434.46: *4* ac.start
1003 def start(self, event):
1004 """Init the completer and start the state handler."""
1005 # We don't need to clear this now that we don't use ContextSniffer.
1006 c = self.c
1007 if c.config.getBool('use-jedi', default=True):
1008 self.completionsDict = {}
1009 if self.use_qcompleter:
1010 self.init_qcompleter(event)
1011 else:
1012 self.init_tabcompleter(event)
1013 #@+node:ekr.20110512170111.14471: *4* ac.strip_brackets
1014 def strip_brackets(self, s):
1015 """Return s with all brackets removed.
1017 This (mostly) ensures that eval will not execute function calls, etc.
1018 """
1019 for ch in '[]{}()':
1020 s = s.replace(ch, '')
1021 return s
1022 #@-others
1023#@+node:ekr.20110312162243.14260: ** class ContextSniffer
1024class ContextSniffer:
1025 """ Class to analyze surrounding context and guess class
1027 For simple dynamic code completion engines.
1028 """
1030 def __init__(self):
1031 self.vars = {}
1032 # Keys are var names; values are list of classes
1033 #@+others
1034 #@+node:ekr.20110312162243.14261: *3* get_classes
1035 def get_classes(self, s, varname):
1036 """Return a list of classes for string s."""
1037 self.push_declarations(s)
1038 aList = self.vars.get(varname, [])
1039 return aList
1040 #@+node:ekr.20110312162243.14262: *3* set_small_context
1041 # def set_small_context(self, body):
1042 # """ Set immediate function """
1043 # self.push_declarations(body)
1044 #@+node:ekr.20110312162243.14263: *3* push_declarations & helper
1045 def push_declarations(self, s):
1046 for line in s.splitlines():
1047 line = line.lstrip()
1048 if line.startswith('#'):
1049 line = line.lstrip('#')
1050 parts = line.split(':')
1051 if len(parts) == 2:
1052 a, b = parts
1053 self.declare(a.strip(), b.strip())
1054 #@+node:ekr.20110312162243.14264: *4* declare
1055 def declare(self, var, klass):
1056 vars = self.vars.get(var, [])
1057 if not vars:
1058 self.vars[var] = vars
1059 vars.append(klass)
1060 #@-others
1061#@+node:ekr.20140813052702.18194: ** class FileNameChooser
1062class FileNameChooser:
1063 """A class encapsulation file selection & completion logic."""
1064 #@+others
1065 #@+node:ekr.20140813052702.18195: *3* fnc.__init__
1066 def __init__(self, c):
1067 """Ctor for FileNameChooser class."""
1068 self.c = c
1069 self.k = c.k
1070 assert c and c.k
1071 self.log = c.frame.log or g.NullObject()
1072 self.callback = None
1073 self.filterExt = None
1074 self.log = None # inited later.
1075 self.prompt = None
1076 self.tabName = None
1077 #@+node:ekr.20140813052702.18196: *3* fnc.compute_tab_list
1078 def compute_tab_list(self):
1079 """Compute the list of completions."""
1080 path = self.get_label()
1081 # #215: insert-file-name doesn't process ~
1082 path = g.os_path_expanduser(path)
1083 sep = os.path.sep
1084 if g.os_path_exists(path):
1085 if g.os_path_isdir(path):
1086 if path.endswith(os.sep):
1087 aList = g.glob_glob(path + '*')
1088 else:
1089 aList = g.glob_glob(path + sep + '*')
1090 tabList = [z + sep if g.os_path_isdir(z) else z for z in aList]
1091 else:
1092 # An existing file.
1093 tabList = [path]
1094 else:
1095 if path and path.endswith(sep):
1096 path = path[:-1]
1097 aList = g.glob_glob(path + '*')
1098 tabList = [z + sep if g.os_path_isdir(z) else z for z in aList]
1099 if self.filterExt:
1100 for ext in self.filterExt:
1101 tabList = [z for z in tabList if not z.endswith(ext)]
1102 tabList = [g.os_path_normslashes(z) for z in tabList]
1103 junk, common_prefix = g.itemsMatchingPrefixInList(path, tabList)
1104 return common_prefix, tabList
1105 #@+node:ekr.20140813052702.18197: *3* fnc.do_back_space
1106 def do_back_space(self):
1107 """Handle a back space."""
1108 w = self.c.k.w
1109 if w and w.hasSelection():
1110 # s = w.getAllText()
1111 i, j = w.getSelectionRange()
1112 w.delete(i, j)
1113 s = self.get_label()
1114 else:
1115 s = self.get_label()
1116 if s:
1117 s = s[:-1]
1118 self.set_label(s)
1119 if s:
1120 common_prefix, tabList = self.compute_tab_list()
1121 # Do *not* extend the label to the common prefix.
1122 else:
1123 tabList = []
1124 self.show_tab_list(tabList)
1125 #@+node:ekr.20140813052702.18198: *3* fnc.do_char
1126 def do_char(self, char):
1127 """Handle a non-special character."""
1128 w = self.c.k.w
1129 if w and w.hasSelection:
1130 # s = w.getAllText()
1131 i, j = w.getSelectionRange()
1132 w.delete(i, j)
1133 w.setInsertPoint(i)
1134 w.insert(i, char)
1135 else:
1136 self.extend_label(char)
1137 common_prefix, tabList = self.compute_tab_list()
1138 self.show_tab_list(tabList)
1139 if common_prefix:
1140 if 0:
1141 # This is a bit *too* helpful.
1142 # It's too easy to type ahead by mistake.
1143 # Instead, completion should happen only when the user types <tab>.
1144 self.set_label(common_prefix)
1145 # Recompute the tab list.
1146 common_prefix, tabList = self.compute_tab_list()
1147 self.show_tab_list(tabList)
1148 if len(tabList) == 1:
1149 # Automatically complete the typing only if there is only one item in the list.
1150 self.set_label(common_prefix)
1151 else:
1152 # Restore everything.
1153 self.set_label(self.get_label()[:-1])
1154 self.extend_label(char)
1155 #@+node:ekr.20140813052702.18199: *3* fnc.do_tab
1156 def do_tab(self):
1157 """Handle tab completion."""
1158 old = self.get_label()
1159 common_prefix, tabList = self.compute_tab_list()
1160 self.show_tab_list(tabList)
1161 if len(tabList) == 1:
1162 common_prefix = tabList[0]
1163 self.set_label(common_prefix)
1164 elif len(common_prefix) > len(old):
1165 self.set_label(common_prefix)
1166 #@+node:ekr.20140813052702.18200: *3* fnc.get_file_name (entry)
1167 def get_file_name(self, event, callback, filterExt, prompt, tabName):
1168 """Get a file name, supporting file completion."""
1169 c, k = self.c, self.c.k
1170 tag = 'get-file-name'
1171 state = k.getState(tag)
1172 char = event.char if event else ''
1173 if state == 0:
1174 # Re-init all ivars.
1175 self.log = c.frame.log or g.NullObject()
1176 self.callback = callback
1177 self.filterExt = filterExt or ['.pyc', '.bin',]
1178 self.prompt = prompt
1179 self.tabName = tabName
1180 join = g.os_path_finalize_join
1181 finalize = g.os_path_finalize
1182 normslashes = g.os_path_normslashes
1183 # #467: Add setting for preferred directory.
1184 directory = c.config.getString('initial-chooser-directory')
1185 if directory:
1186 directory = finalize(directory)
1187 if not g.os_path_exists(directory):
1188 g.es_print('@string initial-chooser-directory not found',
1189 normslashes(directory))
1190 directory = None
1191 if not directory:
1192 directory = finalize(os.curdir)
1193 # Init the label and state.
1194 tail = k.functionTail and k.functionTail.strip()
1195 label = join(directory, tail) if tail else directory + os.sep
1196 self.set_label(normslashes(label))
1197 k.setState(tag, 1, self.get_file_name)
1198 self.log.selectTab(self.tabName)
1199 junk, tabList = self.compute_tab_list()
1200 self.show_tab_list(tabList)
1201 c.minibufferWantsFocus()
1202 elif char == 'Escape':
1203 k.keyboardQuit()
1204 elif char in ('\n', 'Return'):
1205 self.log.deleteTab(self.tabName)
1206 path = self.get_label()
1207 k.keyboardQuit()
1208 if self.callback:
1209 # pylint: disable=not-callable
1210 self.callback(path)
1211 else:
1212 g.trace('no callback')
1213 elif char in ('\t', 'Tab'):
1214 self.do_tab()
1215 c.minibufferWantsFocus()
1216 elif char in ('\b', 'BackSpace'):
1217 self.do_back_space()
1218 c.minibufferWantsFocus()
1219 elif k.isPlainKey(char):
1220 self.do_char(char)
1221 else:
1222 pass
1223 #@+node:ekr.20140813052702.18201: *3* fnc.extend/get/set_label
1224 def extend_label(self, s):
1225 """Extend the label by s."""
1226 self.c.k.extendLabel(s, select=False, protect=False)
1228 def get_label(self):
1229 """Return the label, not including the prompt."""
1230 return self.c.k.getLabel(ignorePrompt=True)
1232 def set_label(self, s):
1233 """Set the label after the prompt to s. The prompt never changes."""
1234 self.c.k.setLabel(self.prompt, protect=True)
1235 self.c.k.extendLabel(s or '', select=False, protect=False)
1236 #@+node:ekr.20140813052702.18202: *3* fnc.show_tab_list
1237 def show_tab_list(self, tabList):
1238 """Show the tab list in the log tab."""
1239 self.log.clearTab(self.tabName)
1240 s = g.os_path_finalize(os.curdir) + os.sep
1241 es = []
1242 for path in tabList:
1243 theDir, fileName = g.os_path_split(path)
1244 s = theDir if path.endswith(os.sep) else fileName
1245 s = fileName or g.os_path_basename(theDir) + os.sep
1246 es.append(s)
1247 g.es('', '\n'.join(es), tabName=self.tabName)
1248 #@-others
1249#@+node:ekr.20140816165728.18940: ** class GetArg
1250class GetArg:
1251 """
1252 A class encapsulating all k.getArg logic.
1254 k.getArg maps to ga.get_arg, which gets arguments in the minibuffer.
1256 For details, see the docstring for ga.get_arg
1257 """
1258 #@+others
1259 #@+node:ekr.20140818052417.18241: *3* ga.birth
1260 #@+node:ekr.20140816165728.18952: *4* ga.__init__
1261 def __init__(self, c, prompt='full-command: ', tabName='Completion'):
1262 """Ctor for GetArg class."""
1263 # Common ivars.
1264 self.c = c
1265 self.k = c.k
1266 assert c
1267 assert c.k
1268 self.log = c.frame.log or g.NullObject()
1269 self.tabName = tabName
1270 # State vars.
1271 self.after_get_arg_state = None, None, None
1272 self.arg_completion = True
1273 self.handler = None
1274 self.tabList = []
1275 # Tab cycling ivars...
1276 self.cycling_prefix = None
1277 self.cycling_index = -1
1278 self.cycling_tabList = []
1279 # The following are k globals.
1280 # k.arg.
1281 # k.argSelectedText
1282 # k.oneCharacterArg
1283 #@+node:ekr.20140817110228.18321: *3* ga.compute_tab_list
1284 # Called from k.doTabCompletion: with tabList = list(c.commandsDict.keys())
1286 def compute_tab_list(self, tabList):
1287 """Compute and show the available completions."""
1288 # Support vim-mode commands.
1289 command = self.get_label()
1290 if self.is_command(command):
1291 tabList, common_prefix = g.itemsMatchingPrefixInList(command, tabList)
1292 return common_prefix, tabList
1293 #
1294 # For now, disallow further completions if something follows the command.
1295 command = self.get_command(command)
1296 return command, [command]
1297 #@+node:ekr.20140816165728.18965: *3* ga.do_back_space (entry)
1298 # Called from k.fullCommand: with defaultTabList = list(c.commandsDict.keys())
1300 def do_back_space(self, tabList, completion=True):
1301 """Handle a backspace and update the completion list."""
1302 k = self.k
1303 self.tabList = tabList[:] if tabList else []
1304 # Update the label.
1305 w = k.w
1306 i, j = w.getSelectionRange()
1307 ins = w.getInsertPoint()
1308 if ins > len(k.mb_prefix):
1309 # Step 1: actually delete the character.
1310 i, j = w.getSelectionRange()
1311 if i == j:
1312 ins -= 1
1313 w.delete(ins)
1314 w.setSelectionRange(ins, ins, insert=ins)
1315 else:
1316 ins = i
1317 w.delete(i, j)
1318 w.setSelectionRange(i, i, insert=ins)
1319 if w.getAllText().strip():
1320 junk, tabList = self.compute_tab_list(self.tabList)
1321 # Do *not* extend the label to the common prefix.
1322 else:
1323 tabList = []
1324 if completion:
1325 # #323.
1326 common_prefix, tabList = self.compute_tab_list(tabList)
1327 self.show_tab_list(tabList)
1328 self.reset_tab_cycling()
1329 #@+node:ekr.20140817110228.18323: *3* ga.do_tab (entry) & helpers
1330 # Used by ga.get_arg and k.fullCommand.
1332 def do_tab(self, tabList, completion=True):
1333 """Handle tab completion when the user hits a tab."""
1334 c = self.c
1335 if completion:
1336 tabList = self.tabList = tabList[:] if tabList else []
1337 common_prefix, tabList = self.compute_tab_list(tabList)
1338 if self.cycling_prefix and not self.cycling_prefix.startswith(common_prefix):
1339 self.cycling_prefix = common_prefix
1340 #
1341 # No tab cycling for completed commands having
1342 # a 'tab_callback' attribute.
1343 if len(tabList) == 1 and self.do_tab_callback():
1344 return
1345 # #323: *Always* call ga.do_tab_list.
1346 self.do_tab_cycling(common_prefix, tabList)
1347 c.minibufferWantsFocus()
1348 #@+node:ekr.20140818145250.18235: *4* ga.do_tab_callback
1349 def do_tab_callback(self):
1350 """
1351 If the command-name handler has a tab_callback,
1352 call handler.tab_callback() and return True.
1353 """
1354 c, k = self.c, self.k
1355 commandName, tail = k.getMinibufferCommandName()
1356 handler = c.commandsDict.get(commandName)
1357 if hasattr(handler, 'tab_callback'):
1358 self.reset_tab_cycling()
1359 k.functionTail = tail # For k.getFileName.
1360 handler.tab_callback()
1361 return True
1362 return False
1363 #@+node:ekr.20140819050118.18317: *4* ga.do_tab_cycling
1364 def do_tab_cycling(self, common_prefix, tabList):
1365 """Put the next (or first) completion in the minibuffer."""
1366 s = self.get_label()
1367 if not common_prefix:
1368 # Leave the minibuffer as it is.
1369 self.show_tab_list(tabList)
1370 # #323.
1371 elif (
1372 self.cycling_prefix and
1373 s.startswith(self.cycling_prefix) and
1374 sorted(self.cycling_tabList) == sorted(tabList) # Bug fix: 2016/10/14
1375 ):
1376 n = self.cycling_index
1377 n = self.cycling_index = n + 1 if n + 1 < len(self.cycling_tabList) else 0
1378 self.set_label(self.cycling_tabList[n])
1379 self.show_tab_list(self.cycling_tabList)
1380 else:
1381 # Restart.
1382 self.show_tab_list(tabList)
1383 self.cycling_tabList = tabList[:]
1384 self.cycling_prefix = common_prefix
1385 self.set_label(common_prefix)
1386 if tabList and common_prefix == tabList[0]:
1387 self.cycling_index = 0
1388 else:
1389 self.cycling_index = -1
1390 #@+node:ekr.20140819050118.18318: *4* ga.reset_tab_cycling
1391 def reset_tab_cycling(self):
1392 """Reset all tab cycling ivars."""
1393 self.cycling_prefix = None
1394 self.cycling_index = -1
1395 self.cycling_tabList = []
1396 #@+node:ekr.20140816165728.18958: *3* ga.extend/get/set_label
1397 # Not useful because k.entendLabel doesn't handle selected text.
1399 if 0:
1401 def extend_label(self, s):
1402 """Extend the label by s."""
1403 self.c.k.extendLabel(s, select=False, protect=False)
1405 def get_label(self):
1406 """Return the label, not including the prompt."""
1407 return self.c.k.getLabel(ignorePrompt=True)
1409 def set_label(self, s):
1410 """Set the label after the prompt to s. The prompt never changes."""
1411 k = self.c.k
1412 # Using k.mb_prefix is simplest. No ga.ivars need be inited.
1413 k.setLabel(k.mb_prefix, protect=True)
1414 k.extendLabel(s or '', select=False, protect=False)
1415 #@+node:ekr.20140816165728.18941: *3* ga.get_arg (entry) & helpers
1416 def get_arg(self, event,
1417 returnKind=None, returnState=None, handler=None,
1418 tabList=None, completion=True, oneCharacter=False,
1419 stroke=None, useMinibuffer=True
1420 ):
1421 #@+<< ga.get_arg docstring >>
1422 #@+node:ekr.20140822051549.18299: *4* << ga.get_arg docstring >>
1423 """
1424 Accumulate an argument. Enter the given return state when done.
1426 Ctrl-G will abort this processing at any time.
1428 All commands needing user input call k.getArg, which just calls ga.get_arg.
1430 The arguments to ga.get_arg are as follows:
1432 event: The event passed to the command.
1434 returnKind=None: A string.
1435 returnState=None, An int.
1436 handler=None, A function.
1438 When the argument is complete, ga.do_end does::
1440 if kind: k.setState(kind,n,handler)
1442 tabList=[]: A list of possible completions.
1444 completion=True: True if completions are enabled.
1446 oneCharacter=False: True if k.arg should be a single character.
1448 stroke=None: The incoming key stroke.
1450 useMinibuffer=True: True: put focus in the minibuffer while accumulating arguments.
1451 False allows sort-lines, for example, to show the selection range.
1453 """
1454 #@-<< ga.get_arg docstring >>
1455 if tabList is None:
1456 tabList = []
1457 c, k = self.c, self.k
1458 state = k.getState('getArg')
1459 c.check_event(event)
1460 c.minibufferWantsFocusNow()
1461 char = event.char if event else ''
1462 if state == 0:
1463 self.do_state_zero(completion, event, handler, oneCharacter,
1464 returnKind, returnState, tabList, useMinibuffer)
1465 return
1466 if char == 'Escape':
1467 k.keyboardQuit()
1468 elif self.should_end(char, stroke):
1469 self.do_end(event, char, stroke)
1470 elif char in ('\t', 'Tab'):
1471 self.do_tab(self.tabList, self.arg_completion)
1472 elif char in ('\b', 'BackSpace'):
1473 self.do_back_space(self.tabList, self.arg_completion)
1474 c.minibufferWantsFocus()
1475 elif k.isFKey(stroke):
1476 # Ignore only F-keys. Ignoring all except plain keys would kill unicode searches.
1477 pass
1478 else:
1479 self.do_char(event, char)
1480 #@+node:ekr.20161019060054.1: *4* ga.cancel_after_state
1481 def cancel_after_state(self):
1483 self.after_get_arg_state = None
1484 #@+node:ekr.20140816165728.18955: *4* ga.do_char
1485 def do_char(self, event, char):
1486 """Handle a non-special character."""
1487 k = self.k
1488 k.updateLabel(event)
1489 # Any plain key resets tab cycling.
1490 self.reset_tab_cycling()
1491 #@+node:ekr.20140817110228.18316: *4* ga.do_end
1492 def do_end(self, event, char, stroke):
1493 """A return or escape has been seen."""
1494 k = self.k
1495 if char == '\t' and char in k.getArgEscapes:
1496 k.getArgEscapeFlag = True
1497 if stroke and stroke in k.getArgEscapes:
1498 k.getArgEscapeFlag = True
1499 # Get the value.
1500 gui_arg = getattr(g.app.gui, 'curses_gui_arg', None)
1501 if k.oneCharacterArg:
1502 k.arg = char
1503 else:
1504 # A hack to support the curses gui.
1505 k.arg = gui_arg or self.get_label()
1506 kind, n, handler = self.after_get_arg_state
1507 if kind:
1508 k.setState(kind, n, handler)
1509 self.log.deleteTab('Completion')
1510 self.reset_tab_cycling()
1511 if handler:
1512 # pylint: disable=not-callable
1513 handler(event)
1514 #@+node:ekr.20140817110228.18317: *4* ga.do_state_zero
1515 def do_state_zero(self, completion, event, handler, oneCharacter,
1516 returnKind, returnState, tabList, useMinibuffer
1517 ):
1518 """Do state 0 processing."""
1519 c, k = self.c, self.k
1520 #
1521 # Set the ga globals...
1522 k.getArgEscapeFlag = False
1523 self.after_get_arg_state = returnKind, returnState, handler
1524 self.arg_completion = completion
1525 self.cycling_prefix = None
1526 self.handler = handler
1527 self.tabList = tabList[:] if tabList else []
1528 #
1529 # Set the k globals...
1530 k.functionTail = None
1531 k.oneCharacterArg = oneCharacter
1532 #
1533 # Do *not* change the label here!
1534 # Enter the next state.
1535 c.widgetWantsFocus(c.frame.body.wrapper)
1536 k.setState('getArg', 1, k.getArg)
1537 # pylint: disable=consider-using-ternary
1538 k.afterArgWidget = event and event.widget or c.frame.body.wrapper
1539 if useMinibuffer:
1540 c.minibufferWantsFocus()
1541 #@+node:ekr.20140818103808.18234: *4* ga.should_end
1542 def should_end(self, char, stroke):
1543 """Return True if ga.get_arg should return."""
1544 k = self.k
1545 return (
1546 char in ('\n', 'Return',) or
1547 k.oneCharacterArg or
1548 stroke and stroke in k.getArgEscapes or
1549 char == '\t' and char in k.getArgEscapes
1550 # The Find Easter Egg.
1551 )
1552 #@+node:ekr.20140818103808.18235: *4* ga.trace_state
1553 def trace_state(self, char, completion, handler, state, stroke):
1554 """Trace the vars and ivars."""
1555 k = self.c.k
1556 g.trace(
1557 'state', state, 'char', repr(char), 'stroke', repr(stroke),
1558 # 'isPlain',k.isPlainKey(stroke),
1559 '\n',
1560 'escapes', k.getArgEscapes,
1561 'completion', self.arg_completion,
1562 'handler', self.handler and self.handler.__name__ or 'None',
1563 )
1564 #@+node:ekr.20140818074502.18222: *3* ga.get_command
1565 def get_command(self, s):
1566 """Return the command part of a minibuffer contents s."""
1567 # #1121.
1568 if ' ' in s:
1569 return s[: s.find(' ')].strip()
1570 return s
1571 #@+node:ekr.20140818085719.18227: *3* ga.get_minibuffer_command_name
1572 def get_minibuffer_command_name(self):
1573 """Return the command name in the minibuffer."""
1574 s = self.get_label()
1575 command = self.get_command(s)
1576 tail = s[len(command) :]
1577 return command, tail
1578 #@+node:ekr.20140818074502.18221: *3* ga.is_command
1579 def is_command(self, s):
1580 """Return False if something, even a blank, follows a command."""
1581 # #1121: only ascii space terminates a command.
1582 return ' ' not in s
1583 #@+node:ekr.20140816165728.18959: *3* ga.show_tab_list & helper
1584 def show_tab_list(self, tabList):
1585 """Show the tab list in the log tab."""
1586 k = self.k
1587 self.log.clearTab(self.tabName)
1588 d = k.computeInverseBindingDict()
1589 data, legend, n = [], False, 0
1590 for commandName in tabList:
1591 dataList = d.get(commandName, [])
1592 if dataList:
1593 for z in dataList:
1594 pane, key = z
1595 s1a = '' if pane in ('all:', 'button:') else f"{pane} "
1596 s1b = k.prettyPrintKey(key)
1597 s1 = s1a + s1b
1598 s2 = self.command_source(commandName)
1599 if s2 != ' ':
1600 legend = True
1601 s3 = commandName
1602 data.append((s1, s2, s3),)
1603 n = max(n, len(s1))
1604 else:
1605 # Bug fix: 2017/03/26
1606 data.append(('', ' ', commandName),)
1607 aList = ['%*s %s %s' % (-n, z1, z2, z3) for z1, z2, z3 in data]
1608 if legend:
1609 aList.extend([
1610 '',
1611 'legend:',
1612 'G leoSettings.leo',
1613 'M myLeoSettings.leo',
1614 'L local .leo File',
1615 ])
1616 g.es('', '\n'.join(aList), tabName=self.tabName)
1617 #@+node:ekr.20150402034643.1: *4* ga.command_source
1618 def command_source(self, commandName):
1619 """
1620 Return the source legend of an @button/@command node.
1621 'G' leoSettings.leo
1622 'M' myLeoSettings.leo
1623 'L' local .leo File
1624 ' ' not an @command or @button node
1625 """
1626 c = self.c
1627 if commandName.startswith('@'):
1628 d = c.commandsDict
1629 func = d.get(commandName)
1630 if hasattr(func, 'source_c'):
1631 c2 = func.source_c
1632 fn2 = c2.shortFileName().lower()
1633 if fn2.endswith('myleosettings.leo'):
1634 return 'M'
1635 if fn2.endswith('leosettings.leo'):
1636 return 'G'
1637 return 'L'
1638 return '?'
1639 return ' '
1640 #@-others
1641#@+node:ekr.20061031131434.74: ** class KeyHandlerClass
1642class KeyHandlerClass:
1643 """
1644 A class to support emacs-style commands.
1645 c.k is an instance of this class.
1646 """
1647 #@+others
1648 #@+node:ekr.20061031131434.75: *3* k.Birth
1649 #@+node:ekr.20061031131434.76: *4* k.__init__& helpers
1650 def __init__(self, c):
1651 """Create a key handler for c."""
1652 self.c = c
1653 self.dispatchEvent = None
1654 self.fnc = None # A singleton defined in k.finishCreate.
1655 self.getArgInstance = None # A singleton defined in k.finishCreate.
1656 self.inited = False # Set at end of finishCreate.
1657 self.killedBindings = [] # A list of commands whose bindings have been set to None in the local file.
1658 self.replace_meta_with_alt = False # True: (Mac only) swap Meta and Alt keys.
1659 self.w = None # Will be None for NullGui.
1660 # Generalize...
1661 self.x_hasNumeric = ['sort-lines', 'sort-fields']
1662 self.altX_prompt = 'full-command: '
1663 # Access to data types defined in leoKeys.py
1664 self.KeyStroke = g.KeyStroke
1665 # Define all ivars...
1666 self.defineExternallyVisibleIvars()
1667 self.defineInternalIvars()
1668 self.reloadSettings()
1669 self.defineSingleLineCommands()
1670 self.defineMultiLineCommands()
1671 self.autoCompleter = AutoCompleterClass(self)
1672 self.qcompleter = None # Set by AutoCompleter.start.
1673 self.setDefaultUnboundKeyAction()
1674 self.setDefaultEditingAction()
1675 #@+node:ekr.20061031131434.78: *5* k.defineExternallyVisibleIvars
1676 def defineExternallyVisibleIvars(self):
1678 self.abbrevOn = False # True: abbreviations are on.
1679 self.arg = '' # The value returned by k.getArg.
1680 self.getArgEscapeFlag = False # True: the user escaped getArg in an unusual way.
1681 self.getArgEscapes = []
1682 self.inputModeName = '' # The name of the input mode, or None.
1683 self.modePrompt = '' # The mode promopt.
1684 self.state = g.bunch(kind=None, n=None, handler=None)
1686 # Remove ???
1687 self.givenArgs = [] # Args specified after the command name in k.simulateCommand.
1688 self.functionTail = None # For commands that take minibuffer arguments.
1689 #@+node:ekr.20061031131434.79: *5* k.defineInternalIvars
1690 def defineInternalIvars(self):
1691 """Define internal ivars of the KeyHandlerClass class."""
1692 self.abbreviationsDict = {} # Abbreviations created by @alias nodes.
1693 # Previously defined bindings...
1694 self.bindingsDict = {} # Keys are Tk key names, values are lists of BindingInfo objects.
1695 # Previously defined binding tags.
1696 self.bindtagsDict = {} # Keys are strings (the tag), values are 'True'
1697 self.commandHistory = []
1698 # Up arrow will select commandHistory[commandIndex]
1699 self.commandIndex = 0 # List/stack of previously executed commands.
1700 # Keys are scope names: 'all','text',etc. or mode names.
1701 # Values are dicts: keys are strokes, values are BindingInfo objects.
1702 self.masterBindingsDict = {}
1703 self.masterGuiBindingsDict = {} # Keys are strokes; value is True.
1704 # Special bindings for k.fullCommand...
1705 self.mb_copyKey = None
1706 self.mb_pasteKey = None
1707 self.mb_cutKey = None
1708 # Keys whose bindings are computed by initSpecialIvars...
1709 self.abortAllModesKey = None
1710 self.autoCompleteForceKey = None
1711 self.demoNextKey = None # New support for the demo.py plugin.
1712 self.demoPrevKey = None # New support for the demo.py plugin.
1713 # Used by k.masterKeyHandler...
1714 self.stroke = None
1715 self.mb_event = None
1716 self.mb_history = []
1717 self.mb_help = False
1718 self.mb_helpHandler = None
1719 # Important: these are defined in k.defineExternallyVisibleIvars...
1720 # self.getArgEscapes = []
1721 # self.getArgEscapeFlag
1722 # For onIdleTime...
1723 self.idleCount = 0
1724 # For modes...
1725 self.modeBindingsDict = {}
1726 self.modeWidget = None
1727 self.silentMode = False
1728 #@+node:ekr.20080509064108.7: *5* k.defineMultiLineCommands
1729 def defineMultiLineCommands(self):
1730 k = self
1731 k.multiLineCommandList = [
1732 # EditCommandsClass
1733 'add-space-to-lines',
1734 'add-tab-to-lines',
1735 'back-page',
1736 'back-page-extend-selection',
1737 'back-paragraph',
1738 'back-paragraph-extend-selection',
1739 'back-sentence',
1740 'back-sentence-extend-selection',
1741 'backward-kill-paragraph',
1742 'beginning-of-buffer',
1743 'beginning-of-buffer-extend-selection',
1744 'center-line',
1745 'center-region',
1746 'clean-all-lines',
1747 'clean-lines',
1748 'downcase-region',
1749 'end-of-buffer',
1750 'end-of-buffer-extend-selection',
1751 'extend-to-paragraph',
1752 'extend-to-sentence',
1753 'fill-paragraph',
1754 'fill-region',
1755 'fill-region-as-paragraph',
1756 'flush-lines',
1757 'forward-page',
1758 'forward-page-extend-selection',
1759 'forward-paragraph',
1760 'forward-paragraph-extend-selection',
1761 'forward-sentence',
1762 'forward-sentence-extend-selection',
1763 'indent-relative',
1764 'indent-rigidly',
1765 'indent-to-comment-column',
1766 'move-lines-down',
1767 'move-lines-up',
1768 'next-line',
1769 'next-line-extend-selection',
1770 'previous-line',
1771 'previous-line-extend-selection',
1772 'remove-blank-lines',
1773 'remove-space-from-lines',
1774 'remove-tab-from-lines',
1775 'reverse-region',
1776 'reverse-sort-lines',
1777 'reverse-sort-lines-ignoring-case',
1778 'scroll-down-half-page',
1779 'scroll-down-line',
1780 'scroll-down-page',
1781 'scroll-up-half-page',
1782 'scroll-up-line',
1783 'scroll-up-page',
1784 'simulate-begin-drag',
1785 'simulate-end-drag',
1786 'sort-columns',
1787 'sort-fields',
1788 'sort-lines',
1789 'sort-lines-ignoring-case',
1790 'split-line',
1791 'tabify',
1792 'transpose-lines',
1793 'untabify',
1794 'upcase-region',
1795 # KeyHandlerCommandsClass
1796 'repeat-complex-command',
1797 # KillBufferCommandsClass
1798 'backward-kill-sentence',
1799 'kill-sentence',
1800 'kill-region',
1801 'kill-region-save',
1802 # QueryReplaceCommandsClass
1803 'query-replace',
1804 'query-replace-regex',
1805 # RectangleCommandsClass
1806 'clear-rectangle',
1807 'close-rectangle',
1808 'delete-rectangle',
1809 'kill-rectangle',
1810 'open-rectangle',
1811 'string-rectangle',
1812 'yank-rectangle',
1813 # SearchCommandsClass
1814 'change',
1815 'change-then-find',
1816 'find-next',
1817 'find-prev',
1818 ]
1819 #@+node:ekr.20080509064108.6: *5* k.defineSingleLineCommands
1820 def defineSingleLineCommands(self):
1821 k = self
1822 # These commands can be executed in the minibuffer.
1823 k.singleLineCommandList = [
1824 # EditCommandsClass
1825 'back-to-indentation',
1826 'back-to-home', # 2010/02/01
1827 'back-char',
1828 'back-char-extend-selection',
1829 'back-word',
1830 'back-word-extend-selection',
1831 'backward-delete-char',
1832 'backward-find-character',
1833 'backward-find-character-extend-selection',
1834 'beginning-of-line',
1835 'beginning-of-line-extend-selection',
1836 'capitalize-word',
1837 'delete-char',
1838 'delete-indentation',
1839 'delete-spaces',
1840 'downcase-word',
1841 'end-of-line',
1842 'end-of-line-extend-selection',
1843 'escape',
1844 'exchange-point-mark',
1845 'extend-to-line',
1846 'extend-to-word',
1847 'find-character',
1848 'find-character-extend-selection',
1849 'find-word',
1850 'find-word-in-line',
1851 'forward-char',
1852 'forward-char-extend-selection',
1853 'forward-end-word',
1854 'forward-end-word-extend-selection',
1855 'forward-word',
1856 'forward-word-extend-selection',
1857 'insert-newline',
1858 'insert-parentheses',
1859 'move-past-close',
1860 'move-past-close-extend-selection',
1861 'newline-and-indent',
1862 'select-all',
1863 'transpose-chars',
1864 'transpose-words',
1865 'upcase-word',
1866 # LeoFind class.
1867 'start-search', # #2041.
1868 # KeyHandlerCommandsClass
1869 'full-command', # #2041.
1870 # 'auto-complete',
1871 # 'negative-argument',
1872 # 'number-command',
1873 # 'number-command-0',
1874 # 'number-command-1',
1875 # 'number-command-2',
1876 # 'number-command-3',
1877 # 'number-command-4',
1878 # 'number-command-5',
1879 # 'number-command-6',
1880 # 'number-command-7',
1881 # 'number-command-8',
1882 # 'universal-argument',
1883 # KillBufferCommandsClass
1884 'backward-kill-word',
1885 'kill-line',
1886 'kill-word',
1887 'kill-ws',
1888 'yank',
1889 'yank-pop',
1890 'zap-to-character',
1891 # leoCommands
1892 'cut-text',
1893 'copy-text',
1894 'paste-text',
1895 # MacroCommandsClass
1896 'call-last-kbd-macro',
1897 # search commands
1898 # 'replace-string', # A special case so Shift-Ctrl-r will work after Ctrl-f.
1899 'set-find-everywhere', # 2011/06/07
1900 'set-find-node-only', # 2011/06/07
1901 'set-find-suboutline-only', # 2011/06/07
1902 'toggle-find-collapses_nodes',
1903 'toggle-find-ignore-case-option',
1904 'toggle-find-in-body-option',
1905 'toggle-find-in-headline-option',
1906 'toggle-find-mark-changes-option',
1907 'toggle-find-mark-finds-option',
1908 'toggle-find-regex-option',
1909 'toggle-find-reverse-option',
1910 'toggle-find-word-option',
1911 'toggle-find-wrap-around-option',
1912 ]
1913 #@+node:ekr.20061031131434.80: *4* k.finishCreate
1914 def finishCreate(self):
1915 """
1916 Complete the construction of the keyHandler class.
1917 c.commandsDict has been created when this is called.
1918 """
1919 c, k = self.c, self
1920 k.w = c.frame.miniBufferWidget # Will be None for NullGui.
1921 k.fnc = FileNameChooser(c) # A singleton. Defined here so that c.k will exist.
1922 k.getArgInstance = GetArg(c) # A singleton. Defined here so that c.k will exist.
1923 # Important: This must be called this now,
1924 # even though LM.load calls g.app.makeAllBindings later.
1925 k.makeAllBindings()
1926 k.initCommandHistory()
1927 k.inited = True
1928 k.setDefaultInputState()
1929 k.resetLabel()
1930 #@+node:ekr.20061101071425: *4* k.oops
1931 def oops(self):
1933 g.trace('Should be defined in subclass:', g.callers(4))
1934 #@+node:ekr.20120217070122.10479: *4* k.reloadSettings
1935 def reloadSettings(self):
1936 # Part 1: These were in the ctor.
1937 c = self.c
1938 getBool = c.config.getBool
1939 getColor = c.config.getColor
1940 self.enable_autocompleter = getBool('enable-autocompleter-initially')
1941 self.enable_calltips = getBool('enable-calltips-initially')
1942 self.ignore_unbound_non_ascii_keys = getBool('ignore-unbound-non-ascii-keys')
1943 self.minibuffer_background_color = getColor(
1944 'minibuffer-background-color') or 'lightblue'
1945 self.minibuffer_foreground_color = getColor(
1946 'minibuffer-foreground-color') or 'black'
1947 self.minibuffer_warning_color = getColor(
1948 'minibuffer-warning-color') or 'lightgrey'
1949 self.minibuffer_error_color = getColor('minibuffer-error-color') or 'red'
1950 self.replace_meta_with_alt = getBool('replace-meta-with-alt')
1951 self.warn_about_redefined_shortcuts = getBool('warn-about-redefined-shortcuts')
1952 # Has to be disabled (default) for AltGr support on Windows
1953 self.enable_alt_ctrl_bindings = c.config.getBool('enable-alt-ctrl-bindings')
1954 # Part 2: These were in finishCreate.
1955 # Set mode colors used by k.setInputState.
1956 bg = c.config.getColor('body-text-background-color') or 'white'
1957 fg = c.config.getColor('body-text-foreground-color') or 'black'
1958 self.command_mode_bg_color = getColor('command-mode-bg-color') or bg
1959 self.command_mode_fg_color = getColor('command-mode-fg-color') or fg
1960 self.insert_mode_bg_color = getColor('insert-mode-bg-color') or bg
1961 self.insert_mode_fg_color = getColor('insert-mode-fg-color') or fg
1962 self.overwrite_mode_bg_color = getColor('overwrite-mode-bg-color') or bg
1963 self.overwrite_mode_fg_color = getColor('overwrite-mode-fg-color') or fg
1964 self.unselected_body_bg_color = getColor('unselected-body-bg-color') or bg
1965 self.unselected_body_fg_color = getColor('unselected-body-fg-color') or bg
1966 #@+node:ekr.20110209093958.15413: *4* k.setDefaultEditingKeyAction
1967 def setDefaultEditingAction(self):
1968 c = self.c
1969 action = c.config.getString('default-editing-state') or 'insert'
1970 action.lower()
1971 if action not in ('command', 'insert', 'overwrite'):
1972 g.trace(f"ignoring default_editing_state: {action}")
1973 action = 'insert'
1974 self.defaultEditingAction = action
1975 #@+node:ekr.20061031131434.82: *4* k.setDefaultUnboundKeyAction
1976 def setDefaultUnboundKeyAction(self, allowCommandState=True):
1977 c, k = self.c, self
1978 defaultAction = c.config.getString('top-level-unbound-key-action') or 'insert'
1979 defaultAction.lower()
1980 if defaultAction == 'command' and not allowCommandState:
1981 self.unboundKeyAction = 'insert'
1982 elif defaultAction in ('command', 'insert', 'overwrite'):
1983 self.unboundKeyAction = defaultAction
1984 else:
1985 g.trace(f"ignoring top_level_unbound_key_action setting: {defaultAction}")
1986 self.unboundKeyAction = 'insert'
1987 self.defaultUnboundKeyAction = self.unboundKeyAction
1988 k.setInputState(self.defaultUnboundKeyAction)
1989 #@+node:ekr.20061031131434.88: *3* k.Binding
1990 #@+node:ekr.20061031131434.89: *4* k.bindKey & helpers
1991 def bindKey(self, pane, shortcut, callback, commandName, modeFlag=False, tag=None):
1992 """
1993 Bind the indicated shortcut (a Tk keystroke) to the callback.
1995 No actual gui bindings are made: only entries in k.masterBindingsDict
1996 and k.bindingsDict.
1998 tag gives the source of the binding.
2000 Return True if the binding was made successfully.
2001 """
2002 k = self
2003 if not shortcut:
2004 # Don't use this method to undo bindings.
2005 return False
2006 if not k.check_bind_key(commandName, pane, shortcut):
2007 return False
2008 aList = k.bindingsDict.get(shortcut, [])
2009 try:
2010 if not shortcut:
2011 stroke = None
2012 elif g.isStroke(shortcut):
2013 stroke = shortcut
2014 assert stroke.s, stroke
2015 else:
2016 assert shortcut, g.callers()
2017 stroke = g.KeyStroke(binding=shortcut)
2018 bi = g.BindingInfo(
2019 kind=tag,
2020 pane=pane,
2021 func=callback,
2022 commandName=commandName,
2023 stroke=stroke)
2024 if shortcut:
2025 k.bindKeyToDict(pane, shortcut, bi)
2026 # Updates k.masterBindingsDict
2027 if shortcut and not modeFlag:
2028 aList = k.remove_conflicting_definitions(
2029 aList, commandName, pane, shortcut)
2030 # 2013/03/02: a real bug fix.
2031 aList.append(bi)
2032 if shortcut:
2033 assert stroke
2034 k.bindingsDict[stroke] = aList
2035 return True
2036 except Exception: # Could be a user error.
2037 if g.unitTesting or not g.app.menuWarningsGiven:
2038 g.es_print('exception binding', shortcut, 'to', commandName)
2039 g.print_exception()
2040 g.app.menuWarningsGiven = True
2041 return False
2043 bindShortcut = bindKey # For compatibility
2044 #@+node:ekr.20120130074511.10228: *5* k.check_bind_key
2045 def check_bind_key(self, commandName, pane, stroke):
2046 """
2047 Return True if the binding of stroke to commandName for the given
2048 pane can be made.
2049 """
2050 # k = self
2051 assert g.isStroke(stroke)
2052 # Give warning and return if we try to bind to Enter or Leave.
2053 for s in ('enter', 'leave'):
2054 if stroke.lower().find(s) > -1:
2055 g.warning('ignoring invalid key binding:', f"{commandName} = {stroke}")
2056 return False
2057 if pane.endswith('-mode'):
2058 g.trace('oops: ignoring mode binding', stroke, commandName, g.callers())
2059 return False
2060 return True
2061 #@+node:ekr.20120130074511.10227: *5* k.kill_one_shortcut
2062 def kill_one_shortcut(self, stroke):
2063 """
2064 Update the *configuration* dicts so that c.config.getShortcut(name)
2065 will return None for all names *presently* bound to the stroke.
2066 """
2067 c = self.c
2068 lm = g.app.loadManager
2069 if 0:
2070 # This does not fix 327: Create a way to unbind bindings
2071 assert stroke in (None, 'None', 'none') or g.isStroke(stroke), repr(stroke)
2072 else:
2073 # A crucial shortcut: inverting and uninverting dictionaries is slow.
2074 # Important: the comparison is valid regardless of the type of stroke.
2075 if stroke in (None, 'None', 'none'):
2076 return
2077 assert g.isStroke(stroke), stroke
2078 d = c.config.shortcutsDict
2079 if d is None:
2080 d = g.TypedDict( # was TypedDictOfLists.
2081 name='empty shortcuts dict',
2082 keyType=type('commandName'),
2083 valType=g.BindingInfo,
2084 )
2085 inv_d = lm.invert(d)
2086 inv_d[stroke] = []
2087 c.config.shortcutsDict = lm.uninvert(inv_d)
2088 #@+node:ekr.20061031131434.92: *5* k.remove_conflicting_definitions
2089 def remove_conflicting_definitions(self, aList, commandName, pane, shortcut):
2091 k = self
2092 result = []
2093 for bi in aList:
2094 if pane in ('button', 'all', bi.pane):
2095 k.kill_one_shortcut(shortcut)
2096 else:
2097 result.append(bi)
2098 return result
2099 #@+node:ekr.20061031131434.93: *5* k.bindKeyToDict
2100 def bindKeyToDict(self, pane, stroke, bi):
2101 """Update k.masterBindingsDict for the stroke."""
2102 # New in Leo 4.4.1: Allow redefintions.
2103 # Called from makeBindingsFromCommandsDict.
2104 k = self
2105 assert g.isStroke(stroke), stroke
2106 d = k.masterBindingsDict.get(pane, {})
2107 d[stroke] = bi
2108 k.masterBindingsDict[pane] = d
2109 #@+node:ekr.20061031131434.94: *5* k.bindOpenWith
2110 def bindOpenWith(self, d):
2111 """Register an open-with command."""
2112 c, k = self.c, self
2113 shortcut = d.get('shortcut') or ''
2114 name = d.get('name')
2115 # The first parameter must be event, and it must default to None.
2117 def openWithCallback(event=None, c=c, d=d):
2118 return c.openWith(d=d)
2120 # Use k.registerCommand to set the shortcuts in the various binding dicts.
2122 commandName = f"open-with-{name.lower()}"
2123 k.registerCommand(
2124 allowBinding=True,
2125 commandName=commandName,
2126 func=openWithCallback,
2127 pane='all',
2128 shortcut=shortcut,
2129 )
2130 #@+node:ekr.20061031131434.95: *4* k.checkBindings
2131 def checkBindings(self):
2132 """
2133 Print warnings if commands do not have any @shortcut entry.
2134 The entry may be `None`, of course."""
2135 c, k = self.c, self
2136 if not c.config.getBool('warn-about-missing-settings'):
2137 return
2138 for name in sorted(c.commandsDict):
2139 abbrev = k.abbreviationsDict.get(name)
2140 key = c.frame.menu.canonicalizeMenuName(abbrev or name)
2141 key = key.replace('&', '')
2142 if not c.config.exists(key, 'shortcut'):
2143 if abbrev:
2144 g.trace(f"No shortcut for abbrev {name} -> {abbrev} = {key}")
2145 else:
2146 g.trace(f"No shortcut for {name} = {key}")
2147 #@+node:ekr.20061031131434.97: *4* k.completeAllBindings
2148 def completeAllBindings(self, w=None):
2149 """
2150 Make an actual binding in *all* the standard places.
2152 The event will go to k.masterKeyHandler as always, so nothing really changes.
2153 except that k.masterKeyHandler will know the proper stroke.
2154 """
2155 k = self
2156 for stroke in k.bindingsDict:
2157 assert g.isStroke(stroke), repr(stroke)
2158 k.makeMasterGuiBinding(stroke, w=w)
2159 #@+node:ekr.20061031131434.96: *4* k.completeAllBindingsForWidget
2160 def completeAllBindingsForWidget(self, w):
2161 """Make all a master gui binding for widget w."""
2162 k = self
2163 for stroke in k.bindingsDict:
2164 assert g.isStroke(stroke), repr(stroke)
2165 k.makeMasterGuiBinding(stroke, w=w)
2166 #@+node:ekr.20070218130238: *4* k.dumpMasterBindingsDict
2167 def dumpMasterBindingsDict(self):
2168 """Dump k.masterBindingsDict."""
2169 k = self
2170 d = k.masterBindingsDict
2171 g.pr('\nk.masterBindingsDict...\n')
2172 for key in sorted(d):
2173 g.pr(key, '-' * 40)
2174 d2 = d.get(key)
2175 for key2 in sorted(d2):
2176 bi = d2.get(key2)
2177 g.pr(f"{key2:20} {bi.commandName}")
2178 #@+node:ekr.20061031131434.99: *4* k.initAbbrev & helper
2179 def initAbbrev(self):
2180 c = self.c
2181 d = c.config.getAbbrevDict()
2182 if d:
2183 for key in d:
2184 commandName = d.get(key)
2185 if commandName.startswith('press-') and commandName.endswith('-button'):
2186 pass # Must be done later in k.registerCommand.
2187 else:
2188 self.initOneAbbrev(commandName, key)
2189 #@+node:ekr.20130924035029.12741: *5* k.initOneAbbrev
2190 def initOneAbbrev(self, commandName, key):
2191 """Enter key as an abbreviation for commandName in c.commandsDict."""
2192 c = self.c
2193 if c.commandsDict.get(key):
2194 g.trace('ignoring duplicate abbrev: %s', key)
2195 else:
2196 func = c.commandsDict.get(commandName)
2197 if func:
2198 c.commandsDict[key] = func
2199 else:
2200 g.warning('bad abbrev:', key, 'unknown command name:', commandName)
2201 #@+node:ekr.20061031131434.101: *4* k.initSpecialIvars
2202 def initSpecialIvars(self):
2203 """Set ivars for special keystrokes from previously-existing bindings."""
2204 c, k = self.c, self
2205 warn = c.config.getBool('warn-about-missing-settings')
2206 for ivar, commandName in (
2207 ('abortAllModesKey', 'keyboard-quit'),
2208 ('autoCompleteForceKey', 'auto-complete-force'),
2209 ('demoNextKey', 'demo-next'),
2210 ('demoPrevKey', 'demo-prev'),
2211 ):
2212 junk, aList = c.config.getShortcut(commandName)
2213 aList, found = aList or [], False
2214 for pane in ('text', 'all'):
2215 for bi in aList:
2216 if bi.pane == pane:
2217 setattr(k, ivar, bi.stroke)
2218 found = True
2219 break
2220 if not found and warn:
2221 g.trace(f"no setting for {commandName}")
2222 #@+node:ekr.20061031131434.98: *4* k.makeAllBindings
2223 def makeAllBindings(self):
2224 """Make all key bindings in all of Leo's panes."""
2225 k = self
2226 k.bindingsDict = {}
2227 k.addModeCommands()
2228 k.makeBindingsFromCommandsDict()
2229 k.initSpecialIvars()
2230 k.initAbbrev()
2231 k.completeAllBindings()
2232 k.checkBindings()
2233 #@+node:ekr.20061031131434.102: *4* k.makeBindingsFromCommandsDict
2234 def makeBindingsFromCommandsDict(self):
2235 """Add bindings for all entries in c.commandsDict."""
2236 c, k = self.c, self
2237 d = c.commandsDict
2238 #
2239 # Step 1: Create d2.
2240 # Keys are strokes. Values are lists of bi with bi.stroke == stroke.
2241 d2 = g.TypedDict( # was TypedDictOfLists.
2242 name='makeBindingsFromCommandsDict helper dict',
2243 keyType=g.KeyStroke,
2244 valType=g.BindingInfo,
2245 )
2246 for commandName in sorted(d):
2247 command = d.get(commandName)
2248 key, aList = c.config.getShortcut(commandName)
2249 for bi in aList:
2250 # Important: bi.stroke is already canonicalized.
2251 stroke = bi.stroke
2252 bi.commandName = commandName
2253 if stroke:
2254 assert g.isStroke(stroke)
2255 d2.add_to_list(stroke, bi)
2256 #
2257 # Step 2: make the bindings.
2258 for stroke in sorted(d2.keys()):
2259 aList2 = d2.get(stroke)
2260 for bi in aList2:
2261 commandName = bi.commandName
2262 command = c.commandsDict.get(commandName)
2263 tag = bi.kind
2264 pane = bi.pane
2265 if stroke and not pane.endswith('-mode'):
2266 k.bindKey(pane, stroke, command, commandName, tag=tag)
2267 #@+node:ekr.20061031131434.103: *4* k.makeMasterGuiBinding
2268 def makeMasterGuiBinding(self, stroke, w=None):
2269 """Make a master gui binding for stroke in pane w, or in all the standard widgets."""
2270 c, k = self.c, self
2271 f = c.frame
2272 if w:
2273 widgets = [w]
2274 else:
2275 # New in Leo 4.5: we *must* make the binding in the binding widget.
2276 bindingWidget = (
2277 f.tree
2278 and hasattr(f.tree, 'bindingWidget')
2279 and f.tree.bindingWidget
2280 or None)
2281 wrapper = f.body and hasattr(f.body, 'wrapper') and f.body.wrapper or None
2282 canvas = f.tree and hasattr(f.tree, 'canvas') and f.tree.canvas or None
2283 widgets = [c.miniBufferWidget, wrapper, canvas, bindingWidget]
2284 for w in widgets:
2285 if not w:
2286 continue
2287 # Make the binding only if no binding for the stroke exists in the widget.
2288 aList = k.masterGuiBindingsDict.get(stroke, [])
2289 if w not in aList:
2290 aList.append(w)
2291 k.masterGuiBindingsDict[stroke] = aList
2292 #@+node:ekr.20150402111403.1: *3* k.Command history
2293 #@+node:ekr.20150402111413.1: *4* k.addToCommandHistory
2294 def addToCommandHistory(self, commandName):
2295 """Add a name to the command history."""
2296 k = self
2297 h = k.commandHistory
2298 if commandName in h:
2299 h.remove(commandName)
2300 h.append(commandName)
2301 k.commandIndex = None
2302 #@+node:ekr.20150402165918.1: *4* k.commandHistoryDown
2303 def commandHistoryFwd(self):
2304 """
2305 Move down the Command History - fall off the bottom (return empty string)
2306 if necessary
2307 """
2308 k = self
2309 h, i = k.commandHistory, k.commandIndex
2310 if h:
2311 commandName = ''
2312 if i == len(h) - 1:
2313 # fall off the bottom
2314 i = None
2315 elif i is not None:
2316 # move to next down in list
2317 i += 1
2318 commandName = h[i]
2319 k.commandIndex = i
2320 k.setLabel(k.mb_prefix + commandName)
2321 #@+node:ekr.20150402171131.1: *4* k.commandHistoryUp
2322 def commandHistoryBackwd(self):
2323 """
2324 Return the previous entry in the Command History - stay at the top
2325 if we are there
2326 """
2327 k = self
2328 h, i = k.commandHistory, k.commandIndex
2329 if h:
2330 if i is None:
2331 # first time in - set to last entry
2332 i = len(h) - 1
2333 elif i > 0:
2334 i -= 1
2335 commandName = h[i]
2336 k.commandIndex = i
2337 k.setLabel(k.mb_prefix + commandName)
2338 #@+node:ekr.20150425143043.1: *4* k.initCommandHistory
2339 def initCommandHistory(self):
2340 """Init command history from @data command-history nodes."""
2341 k, c = self, self.c
2342 aList = c.config.getData('history-list') or []
2343 for command in reversed(aList):
2344 k.addToCommandHistory(command)
2346 def resetCommandHistory(self):
2347 """ reset the command history index to indicate that
2348 we are pointing 'past' the last entry
2349 """
2350 self.commandIndex = None
2351 #
2352 #@+node:ekr.20150402111935.1: *4* k.sortCommandHistory
2353 def sortCommandHistory(self):
2354 """Sort the command history."""
2355 k = self
2356 k.commandHistory.sort()
2357 k.commandIndex = None
2358 #@+node:ekr.20061031131434.104: *3* k.Dispatching
2359 #@+node:ekr.20061031131434.111: *4* k.fullCommand (alt-x) & helper
2360 @cmd('full-command')
2361 def fullCommand(
2362 self,
2363 event,
2364 specialStroke=None,
2365 specialFunc=None,
2366 help=False,
2367 helpHandler=None,
2368 ):
2369 """Handle 'full-command' (alt-x) mode."""
2370 try:
2371 c, k = self.c, self
2372 state = k.getState('full-command')
2373 helpPrompt = 'Help for command: '
2374 c.check_event(event)
2375 ch = char = event.char if event else ''
2376 if state == 0:
2377 k.mb_event = event # Save the full event for later.
2378 k.setState('full-command', 1, handler=k.fullCommand)
2379 prompt = helpPrompt if help else k.altX_prompt
2380 k.setLabelBlue(prompt)
2381 k.mb_help = help
2382 k.mb_helpHandler = helpHandler
2383 c.minibufferWantsFocus()
2384 elif char == 'Ins' or k.isFKey(char):
2385 pass
2386 elif char == 'Escape':
2387 k.keyboardQuit()
2388 elif char == 'Down':
2389 k.commandHistoryFwd()
2390 elif char == 'Up':
2391 k.commandHistoryBackwd()
2392 elif char in ('\n', 'Return'):
2393 # Fix bug 157: save and restore the selection.
2394 w = k.mb_event and k.mb_event.w
2395 if w and hasattr(w, 'hasSelection') and w.hasSelection():
2396 sel1, sel2 = w.getSelectionRange()
2397 ins = w.getInsertPoint()
2398 c.frame.log.deleteTab('Completion')
2399 w.setSelectionRange(sel1, sel2, insert=ins)
2400 else:
2401 c.frame.log.deleteTab('Completion')
2402 # 2016/04/27
2403 if k.mb_help:
2404 s = k.getLabel()
2405 commandName = s[len(helpPrompt) :].strip()
2406 k.clearState()
2407 k.resetLabel()
2408 if k.mb_helpHandler:
2409 k.mb_helpHandler(commandName)
2410 else:
2411 s = k.getLabel(ignorePrompt=True)
2412 commandName = s.strip()
2413 ok = k.callAltXFunction(k.mb_event)
2414 if ok:
2415 k.addToCommandHistory(commandName)
2416 elif char in ('\t', 'Tab'):
2417 k.doTabCompletion(list(c.commandsDict.keys()))
2418 c.minibufferWantsFocus()
2419 elif char in ('\b', 'BackSpace'):
2420 k.doBackSpace(list(c.commandsDict.keys()))
2421 c.minibufferWantsFocus()
2422 elif k.ignore_unbound_non_ascii_keys and len(ch) > 1:
2423 if specialStroke:
2424 g.trace(specialStroke)
2425 specialFunc()
2426 c.minibufferWantsFocus()
2427 else:
2428 # Clear the list, any other character besides tab indicates that a new prefix is in effect.
2429 k.mb_tabList = []
2430 k.updateLabel(event)
2431 k.mb_tabListPrefix = k.getLabel()
2432 c.minibufferWantsFocus()
2433 except Exception:
2434 g.es_exception()
2435 self.keyboardQuit()
2436 #@+node:ekr.20061031131434.112: *5* k.callAltXFunction
2437 def callAltXFunction(self, event):
2438 """Call the function whose name is in the minibuffer."""
2439 c, k = self.c, self
2440 k.mb_tabList = []
2441 commandName, tail = k.getMinibufferCommandName()
2442 k.functionTail = tail
2443 if commandName and commandName.isdigit():
2444 # The line number Easter Egg.
2446 def func(event=None):
2447 c.gotoCommands.find_file_line(n=int(commandName))
2449 else:
2450 func = c.commandsDict.get(commandName)
2451 if func:
2452 # These must be done *after* getting the command.
2453 k.clearState()
2454 k.resetLabel()
2455 if commandName != 'repeat-complex-command':
2456 k.mb_history.insert(0, commandName)
2457 w = event and event.widget
2458 if hasattr(w, 'permanent') and not w.permanent:
2459 # In a headline that is being edited.
2460 c.endEditing()
2461 c.bodyWantsFocusNow()
2462 # Change the event widget so we don't refer to the to-be-deleted headline widget.
2463 event.w = event.widget = c.frame.body.wrapper.widget
2464 else:
2465 c.widgetWantsFocusNow(event and event.widget) # So cut-text works, e.g.
2466 try:
2467 func(event)
2468 except Exception:
2469 g.es_exception()
2470 return True
2471 # Show possible completions if the command does not exist.
2472 k.doTabCompletion(list(c.commandsDict.keys()))
2473 return False
2474 #@+node:ekr.20061031131434.114: *3* k.Externally visible commands
2475 #@+node:ekr.20070613133500: *4* k.menuCommandKey
2476 def menuCommandKey(self, event=None):
2477 # This method must exist, but it never gets called.
2478 pass
2479 #@+node:ekr.20061031131434.119: *4* k.printBindings & helper
2480 @cmd('show-bindings')
2481 def printBindings(self, event=None):
2482 """Print all the bindings presently in effect."""
2483 c, k = self.c, self
2484 d = k.bindingsDict
2485 tabName = 'Bindings'
2486 c.frame.log.clearTab(tabName)
2487 legend = '''\
2488 legend:
2489 [ ] leoSettings.leo
2490 [D] default binding
2491 [F] loaded .leo File
2492 [M] myLeoSettings.leo
2493 [@] @mode, @button, @command
2495 '''
2496 if not d:
2497 return g.es('no bindings')
2498 legend = textwrap.dedent(legend)
2499 data = []
2500 for stroke in sorted(d):
2501 assert g.isStroke(stroke), stroke
2502 aList = d.get(stroke, [])
2503 for bi in aList:
2504 s1 = '' if bi.pane == 'all' else bi.pane
2505 s2 = k.prettyPrintKey(stroke)
2506 s3 = bi.commandName
2507 s4 = bi.kind or '<no hash>'
2508 data.append((s1, s2, s3, s4),)
2509 # Print keys by type.
2510 result = []
2511 result.append('\n' + legend)
2512 for prefix in (
2513 'Alt+Ctrl+Shift', 'Alt+Ctrl', 'Alt+Shift', 'Alt', # 'Alt+Key': done by Alt.
2514 'Ctrl+Meta+Shift', 'Ctrl+Meta', 'Ctrl+Shift', 'Ctrl', # Ctrl+Key: done by Ctrl.
2515 'Meta+Key', 'Meta+Shift', 'Meta',
2516 'Shift',
2517 'F', # #1972
2518 # Careful: longer prefixes must come before shorter prefixes.
2519 ):
2520 data2 = []
2521 for item in data:
2522 s1, s2, s3, s4 = item
2523 if s2.startswith(prefix):
2524 data2.append(item)
2525 result.append(f"{prefix} keys...\n")
2526 self.printBindingsHelper(result, data2, prefix=prefix)
2527 # Remove all the items in data2 from data.
2528 # This must be done outside the iterator on data.
2529 for item in data2:
2530 data.remove(item)
2531 # Print all plain bindings.
2532 result.append('Plain keys...\n')
2533 self.printBindingsHelper(result, data, prefix=None)
2534 if not g.unitTesting:
2535 g.es_print('', ''.join(result), tabName=tabName)
2536 k.showStateAndMode()
2537 return result # for unit test.
2538 #@+node:ekr.20061031131434.120: *5* printBindingsHelper
2539 def printBindingsHelper(self, result, data, prefix):
2540 """Helper for k.printBindings"""
2541 c, lm = self.c, g.app.loadManager
2542 data.sort(key=lambda x: x[1])
2543 data2, n = [], 0
2544 for pane, key, commandName, kind in data:
2545 key = key.replace('+Key', '')
2546 letter = lm.computeBindingLetter(c, kind)
2547 pane = f"{pane if pane else 'all':4}: "
2548 left = pane + key # pane and shortcut fields
2549 n = max(n, len(left))
2550 data2.append((letter, left, commandName),)
2551 for z in data2:
2552 letter, left, commandName = z
2553 result.append('%s %*s %s\n' % (letter, -n, left, commandName))
2554 if data:
2555 result.append('\n')
2556 #@+node:ekr.20120520174745.9867: *4* k.printButtons
2557 @cmd('show-buttons')
2558 def printButtons(self, event=None):
2559 """Print all @button and @command commands, their bindings and their source."""
2560 c = self.c
2561 tabName = '@buttons && @commands'
2562 c.frame.log.clearTab(tabName)
2564 def put(s):
2565 g.es('', s, tabName=tabName)
2567 data = []
2568 for aList in [c.config.getButtons(), c.config.getCommands()]:
2569 for z in aList:
2570 p, script = z
2571 c = p.v.context
2572 tag = 'M' if c.shortFileName().endswith('myLeoSettings.leo') else 'G'
2573 data.append((p.h, tag),)
2574 for aList in [g.app.config.atLocalButtonsList, g.app.config.atLocalCommandsList]:
2575 for p in aList:
2576 data.append((p.h, 'L'),)
2577 result = [f"{z[1]} {z[0]}" for z in sorted(data)]
2578 result.extend([
2579 '',
2580 'legend:',
2581 'G leoSettings.leo',
2582 'L local .leo File',
2583 'M myLeoSettings.leo',
2584 ])
2585 put('\n'.join(result))
2586 #@+node:ekr.20061031131434.121: *4* k.printCommands
2587 @cmd('show-commands')
2588 def printCommands(self, event=None):
2589 """Print all the known commands and their bindings, if any."""
2590 c, k = self.c, self
2591 tabName = 'Commands'
2592 c.frame.log.clearTab(tabName)
2593 inverseBindingDict = k.computeInverseBindingDict()
2594 data, n = [], 0
2595 for commandName in sorted(c.commandsDict):
2596 dataList = inverseBindingDict.get(commandName, [('', ''),])
2597 for z in dataList:
2598 pane, key = z
2599 pane = f"{pane} " if pane != 'all:' else ''
2600 key = k.prettyPrintKey(key).replace('+Key', '')
2601 s1 = pane + key
2602 s2 = commandName
2603 n = max(n, len(s1))
2604 data.append((s1, s2),)
2605 # This isn't perfect in variable-width fonts.
2606 lines = ['%*s %s\n' % (-n, z1, z2) for z1, z2 in data]
2607 g.es_print('', ''.join(lines), tabName=tabName)
2608 #@+node:tom.20220320235059.1: *4* k.printCommandsWithDocs
2609 @cmd('show-commands-with-docs')
2610 def printCommandsWithDocs(self, event=None):
2611 """Show all the known commands and their bindings, if any."""
2612 c, k = self.c, self
2613 tabName = 'List'
2614 c.frame.log.clearTab(tabName)
2615 inverseBindingDict = k.computeInverseBindingDict()
2616 data = []
2617 for commandName in sorted(c.commandsDict):
2618 dataList = inverseBindingDict.get(commandName, [('', ''),])
2619 for pane, key in dataList:
2620 key = k.prettyPrintKey(key)
2621 binding = pane + key
2622 cmd = commandName.strip()
2623 doc = f'{c.commandsDict.get(commandName).__doc__}' or ''
2624 if doc == 'None':
2625 doc = ''
2626 # Formatting for multi-line docstring
2627 if doc.count('\n') > 0:
2628 doc = f'\n{doc}\n'
2629 else:
2630 doc = f' {doc}'
2631 if doc.startswith('\n'):
2632 doc.replace('\n', '', 1)
2633 toolong = doc.count('\n') > 5
2634 manylines = False
2635 if toolong:
2636 lines = doc.split('\n')[:4]
2637 lines[-1] += ' ...\n'
2638 doc = '\n'.join(lines)
2639 manylines = True
2640 n = min(2, len(binding))
2641 if manylines:
2642 doc = textwrap.fill(doc, width = 50, initial_indent = ' '*4,
2643 subsequent_indent = ' '*4)
2644 data.append((binding, cmd, doc))
2645 lines = ['[%*s] %s%s\n' % (-n, binding, cmd, doc) for binding, cmd, doc in data]
2646 g.es(''.join(lines), tabName = tabName)
2647 #@+node:ekr.20061031131434.122: *4* k.repeatComplexCommand
2648 @cmd('repeat-complex-command')
2649 def repeatComplexCommand(self, event):
2650 """Repeat the previously executed minibuffer command."""
2651 k = self
2652 # #2286: Always call k.fullCommand.
2653 k.setState('getArg', 0, handler=k.fullCommand)
2654 k.fullCommand(event) # #2334
2655 if not k.mb_history:
2656 k.mb_history = list(reversed(k.commandHistory))
2657 command = k.mb_history[0] if k.mb_history else ''
2658 k.setLabelBlue(f"{k.altX_prompt}", protect=True)
2659 k.extendLabel(command, select=False, protect=False)
2660 #@+node:ekr.20061031131434.123: *4* k.set-xxx-State
2661 @cmd('set-command-state')
2662 def setCommandState(self, event):
2663 """Enter the 'command' editing state."""
2664 k = self
2665 k.setInputState('command', set_border=True)
2666 # This command is also valid in headlines.
2667 # k.c.bodyWantsFocus()
2668 k.showStateAndMode()
2670 @cmd('set-insert-state')
2671 def setInsertState(self, event):
2672 """Enter the 'insert' editing state."""
2673 k = self
2674 k.setInputState('insert', set_border=True)
2675 # This command is also valid in headlines.
2676 # k.c.bodyWantsFocus()
2677 k.showStateAndMode()
2679 @cmd('set-overwrite-state')
2680 def setOverwriteState(self, event):
2681 """Enter the 'overwrite' editing state."""
2682 k = self
2683 k.setInputState('overwrite', set_border=True)
2684 # This command is also valid in headlines.
2685 # k.c.bodyWantsFocus()
2686 k.showStateAndMode()
2687 #@+node:ekr.20061031131434.124: *4* k.toggle-input-state
2688 @cmd('toggle-input-state')
2689 def toggleInputState(self, event=None):
2690 """The toggle-input-state command."""
2691 c, k = self.c, self
2692 default = c.config.getString('top-level-unbound-key-action') or 'insert'
2693 state = k.unboundKeyAction
2694 if default == 'insert':
2695 state = 'command' if state == 'insert' else 'insert'
2696 elif default == 'overwrite':
2697 state = 'command' if state == 'overwrite' else 'overwrite'
2698 else:
2699 state = 'insert' if state == 'command' else 'command' # prefer insert to overwrite.
2700 k.setInputState(state)
2701 k.showStateAndMode()
2702 #@+node:ekr.20061031131434.125: *3* k.Externally visible helpers
2703 #@+node:ekr.20140816165728.18968: *4* Wrappers for GetArg methods
2704 # New in Leo 5.4
2706 def getNextArg(self, handler):
2707 """
2708 Get the next arg. For example, after a Tab in the find commands.
2709 See the docstring for k.get1Arg for examples of its use.
2710 """
2711 # Replace the current handler.
2712 self.getArgInstance.after_get_arg_state = ('getarg', 1, handler)
2713 self.c.minibufferWantsFocusNow()
2715 # New in Leo 5.4
2717 def get1Arg(self, event, handler,
2718 prefix=None, tabList=None, completion=True, oneCharacter=False,
2719 stroke=None, useMinibuffer=True
2720 ):
2721 #@+<< docstring for k.get1arg >>
2722 #@+node:ekr.20161020031633.1: *5* << docstring for k.get1arg >>
2723 """
2724 k.get1Arg: Handle the next character the user types when accumulating
2725 a user argument from the minibuffer. Ctrl-G will abort this processing
2726 at any time.
2728 Commands should use k.get1Arg to get the first minibuffer argument and
2729 k.getNextArg to get all other arguments.
2731 Before going into the many details, let's look at some examples. This
2732 code will work in any class having a 'c' ivar bound to a commander.
2734 Example 1: get one argument from the user:
2736 @g.command('my-command')
2737 def myCommand(self, event):
2738 k = self.c.k
2739 k.setLabelBlue('prompt: ')
2740 k.get1Arg(event, handler=self.myCommand1)
2742 def myCommand1(self, event):
2743 k = self.c.k
2744 # k.arg contains the argument.
2745 # Finish the command.
2746 ...
2747 # Reset the minibuffer.
2748 k.clearState()
2749 k.resetLabel()
2750 k.showStateAndMode()
2752 Example 2: get two arguments from the user:
2754 @g.command('my-command')
2755 def myCommand(self, event):
2756 k = self.c.k
2757 k.setLabelBlue('first prompt: ')
2758 k.get1Arg(event, handler=self.myCommand1)
2760 def myCommand1(self, event):
2761 k = self.c.k
2762 self.arg1 = k.arg
2763 k.extendLabel(' second prompt: ', select=False, protect=True)
2764 k.getNextArg(handler=self.myCommand2)
2766 def myCommand2(self, event):
2767 k = self.c.k
2768 # k.arg contains second argument.
2769 # Finish the command, using self.arg1 and k.arg.
2770 ...
2771 # Reset the minibuffer.
2772 k.clearState()
2773 k.resetLabel()
2774 k.showStateAndMode()
2776 k.get1Arg and k.getNextArg are a convenience methods. They simply pass
2777 their arguments to the get_arg method of the singleton GetArg
2778 instance. This docstring describes k.get1arg and k.getNextArg as if
2779 they were the corresponding methods of the GetArg class.
2781 k.get1Arg is a state machine. Logically, states are tuples (kind, n,
2782 handler) though they aren't represented that way. When the state
2783 machine in the GetArg class is active, the kind is 'getArg'. This
2784 constant has special meaning to Leo's key-handling code.
2786 The arguments to k.get1Arg are as follows:
2788 event: The event passed to the command.
2790 handler=None, An executable. k.get1arg calls handler(event)
2791 when the user completes the argument by typing
2792 <Return> or (sometimes) <tab>.
2794 tabList=[]: A list of possible completions.
2796 completion=True: True if completions are enabled.
2798 oneCharacter=False: True if k.arg should be a single character.
2800 stroke=None: The incoming key stroke.
2802 useMinibuffer=True: True: put focus in the minibuffer while accumulating arguments.
2803 False allows sort-lines, for example, to show the selection range.
2805 """
2806 #@-<< docstring for k.get1arg >>
2807 returnKind, returnState = None, None
2808 assert handler, g.callers()
2809 self.getArgInstance.get_arg(event, returnKind, returnState, handler,
2810 tabList, completion, oneCharacter, stroke, useMinibuffer)
2812 def getArg(self, event,
2813 returnKind=None, returnState=None, handler=None,
2814 prefix=None, tabList=None, completion=True, oneCharacter=False,
2815 stroke=None, useMinibuffer=True
2816 ):
2817 """Convenience method mapping k.getArg to ga.get_arg."""
2818 self.getArgInstance.get_arg(event, returnKind, returnState, handler,
2819 tabList, completion, oneCharacter, stroke, useMinibuffer)
2821 def doBackSpace(self, tabList, completion=True):
2822 """Convenience method mapping k.doBackSpace to ga.do_back_space."""
2823 self.getArgInstance.do_back_space(tabList, completion)
2825 def doTabCompletion(self, tabList):
2826 """Convenience method mapping k.doTabCompletion to ga.do_tab."""
2827 self.getArgInstance.do_tab(tabList)
2829 def getMinibufferCommandName(self):
2830 """
2831 Convenience method mapping k.getMinibufferCommandName to
2832 ga.get_minibuffer_command_name.
2833 """
2834 return self.getArgInstance.get_minibuffer_command_name()
2835 #@+node:ekr.20061031131434.130: *4* k.keyboardQuit
2836 @cmd('keyboard-quit')
2837 def keyboardQuit(self, event=None, setFocus=True):
2838 """Clears the state and the minibuffer label."""
2839 c, k = self.c, self
2840 if g.app.quitting:
2841 return
2842 c.endEditing()
2843 # Completely clear the mode.
2844 if setFocus:
2845 c.frame.log.deleteTab('Mode')
2846 c.frame.log.hideTab('Completion')
2847 if k.inputModeName:
2848 k.endMode()
2849 # Complete clear the state.
2850 k.state.kind = None
2851 k.state.n = None
2852 k.clearState()
2853 k.resetLabel()
2854 if setFocus:
2855 c.bodyWantsFocus()
2856 # At present, only the auto-completer suppresses this.
2857 k.setDefaultInputState()
2858 if c.vim_mode and c.vimCommands:
2859 c.vimCommands.reset(setFocus=setFocus)
2860 else:
2861 # This was what caused the unwanted scrolling.
2862 k.showStateAndMode(setFocus=setFocus)
2863 k.resetCommandHistory()
2864 #@+node:ekr.20061031131434.126: *4* k.manufactureKeyPressForCommandName (only for unit tests!)
2865 def manufactureKeyPressForCommandName(self, w, commandName):
2866 """
2867 Implement a command by passing a keypress to the gui.
2869 **Only unit tests use this method.**
2870 """
2871 c, k = self.c, self
2872 # Unit tests do not ordinarily read settings files.
2873 stroke = k.getStrokeForCommandName(commandName)
2874 if stroke is None:
2875 # Create the stroke and binding info data.
2876 stroke = g.KeyStroke('Ctrl+F1')
2877 bi = g.BindingInfo(
2878 kind='manufactured-binding',
2879 commandName=commandName,
2880 func=None,
2881 pane='all',
2882 stroke=stroke,
2883 )
2884 # Make the binding!
2885 # k.masterBindingsDict keys: scope names; values: masterBindingDicts (3)
2886 # Interior masterBindingDicts: Keys are strokes; values are BindingInfo objects.
2887 d = k.masterBindingsDict
2888 d2 = d.get('all', {})
2889 d2[stroke] = bi
2890 d['all'] = d2
2891 assert g.isStroke(stroke), (commandName, stroke.__class__.__name__)
2892 shortcut = stroke.s
2893 shortcut = g.checkUnicode(shortcut)
2894 if shortcut and w:
2895 g.app.gui.set_focus(c, w)
2896 g.app.gui.event_generate(c, None, shortcut, w)
2897 else:
2898 message = f"no shortcut for {commandName}"
2899 if g.unitTesting:
2900 raise AttributeError(message)
2901 g.error(message)
2902 #@+node:ekr.20071212104050: *4* k.overrideCommand
2903 def overrideCommand(self, commandName, func):
2904 # Override entries in c.k.masterBindingsDict
2905 k = self
2906 d = k.masterBindingsDict
2907 for key in d:
2908 d2 = d.get(key)
2909 for key2 in d2:
2910 bi = d2.get(key2)
2911 if bi.commandName == commandName:
2912 bi.func = func
2913 d2[key2] = bi
2914 #@+node:ekr.20061031131434.131: *4* k.registerCommand
2915 def registerCommand(self, commandName, func,
2916 allowBinding=False,
2917 pane='all',
2918 shortcut=None, # Must be None unless allowBindings is True.
2919 ** kwargs
2920 ):
2921 """
2922 Make the function available as a minibuffer command.
2924 You can wrap any method in a callback function, so the
2925 restriction to functions is not significant.
2927 Ignore the 'shortcut' arg unless 'allowBinding' is True.
2929 Only k.bindOpenWith and the mod_scripting.py plugin should set
2930 allowBinding.
2931 """
2932 c, k = self.c, self
2933 if not func:
2934 g.es_print('Null func passed to k.registerCommand\n', commandName)
2935 return
2936 f = c.commandsDict.get(commandName)
2937 if f and f.__name__ != func.__name__:
2938 g.trace('redefining', commandName, f, '->', func)
2939 c.commandsDict[commandName] = func
2940 # Warn about deprecated arguments.
2941 if shortcut and not allowBinding:
2942 g.es_print('The "shortcut" keyword arg to k.registerCommand will be ignored')
2943 g.es_print('Called from', g.callers())
2944 shortcut = None
2945 for arg, val in kwargs.items():
2946 if val is not None:
2947 g.es_print(f'The "{arg}" keyword arg to k.registerCommand is deprecated')
2948 g.es_print('Called from', g.callers())
2949 # Make requested bindings, even if a warning has been given.
2950 # This maintains strict compatibility with existing plugins and scripts.
2951 k.registerCommandShortcut(
2952 commandName=commandName,
2953 func=func,
2954 pane=pane,
2955 shortcut=shortcut,
2956 )
2957 #@+node:ekr.20171124043747.1: *4* k.registerCommandShortcut
2958 def registerCommandShortcut(self, commandName, func, pane, shortcut):
2959 """
2960 Register a shortcut for the a command.
2962 **Important**: Bindings created here from plugins can not be overridden.
2963 This includes @command and @button bindings created by mod_scripting.py.
2964 """
2965 c, k = self.c, self
2966 is_local = c.shortFileName() not in ('myLeoSettings.leo', 'leoSettings.leo')
2967 assert not g.isStroke(shortcut)
2968 if shortcut:
2969 stroke = g.KeyStroke(binding=shortcut) if shortcut else None
2970 elif commandName.lower() == 'shortcut': # Causes problems.
2971 stroke = None
2972 elif is_local:
2973 # 327: Don't get defaults when handling a local file.
2974 stroke = None
2975 else:
2976 # Try to get a stroke from leoSettings.leo.
2977 stroke = None
2978 junk, aList = c.config.getShortcut(commandName)
2979 for bi in aList:
2980 if bi.stroke and not bi.pane.endswith('-mode'):
2981 stroke = bi.stroke
2982 pane = bi.pane # 2015/05/11.
2983 break
2984 if stroke:
2985 k.bindKey(pane, stroke, func, commandName, tag='register-command') # Must be a stroke.
2986 k.makeMasterGuiBinding(stroke) # Must be a stroke.
2987 # Fixup any previous abbreviation to press-x-button commands.
2988 if commandName.startswith('press-') and commandName.endswith('-button'):
2989 d = c.config.getAbbrevDict() # Keys are full command names, values are abbreviations.
2990 if commandName in list(d.values()):
2991 for key in d:
2992 if d.get(key) == commandName:
2993 c.commandsDict[key] = c.commandsDict.get(commandName)
2994 break
2995 #@+node:ekr.20061031131434.127: *4* k.simulateCommand
2996 def simulateCommand(self, commandName, event=None):
2997 """Execute a Leo command by name."""
2998 c = self.c
2999 if not event:
3000 # Create a default key event.
3001 event = g.app.gui.create_key_event(c)
3002 c.doCommandByName(commandName, event)
3003 #@+node:ekr.20140813052702.18203: *4* k.getFileName
3004 def getFileName(self, event, callback=None,
3005 filterExt=None, prompt='Enter File Name: ', tabName='Dired'
3006 ):
3007 """Get a file name from the minibuffer."""
3008 k = self
3009 k.fnc.get_file_name(event, callback, filterExt, prompt, tabName)
3010 #@+node:ekr.20061031131434.145: *3* k.Master event handlers
3011 #@+node:ekr.20061031131434.146: *4* k.masterKeyHandler & helpers
3012 def masterKeyHandler(self, event):
3013 """The master key handler for almost all key bindings."""
3014 trace = 'keys' in g.app.debug
3015 c, k = self.c, self
3016 # Setup...
3017 if trace:
3018 g.trace(repr(k.state.kind), repr(event.char), repr(event.stroke))
3019 k.checkKeyEvent(event)
3020 k.setEventWidget(event)
3021 k.traceVars(event)
3022 # Order is very important here...
3023 if k.isSpecialKey(event):
3024 return
3025 if k.doKeyboardQuit(event):
3026 return
3027 if k.doDemo(event):
3028 return
3029 if k.doMode(event):
3030 return
3031 if k.doVim(event):
3032 return
3033 if k.doBinding(event):
3034 return
3035 # Handle abbreviations.
3036 if k.abbrevOn and c.abbrevCommands.expandAbbrev(event, event.stroke):
3037 return
3038 # Handle the character given by event *without*
3039 # executing any command that might be bound to it.
3040 c.insertCharFromEvent(event)
3041 #@+node:ekr.20200524151214.1: *5* Setup...
3042 #@+node:ekr.20180418040158.1: *6* k.checkKeyEvent
3043 def checkKeyEvent(self, event):
3044 """Perform sanity checks on the incoming event."""
3045 # These assert's should be safe, because eventFilter
3046 # calls k.masterKeyHandler inside a try/except block.
3047 c = self.c
3048 assert event is not None
3049 c.check_event(event)
3050 assert hasattr(event, 'char')
3051 assert hasattr(event, 'stroke')
3052 if not hasattr(event, 'widget'):
3053 event.widget = None
3054 assert g.isStrokeOrNone(event.stroke)
3055 if event:
3056 assert event.stroke.s not in g.app.gui.ignoreChars, repr(event.stroke.s)
3057 # A continuous unit test, better than "@test k.isPlainKey".
3058 #@+node:ekr.20180418034305.1: *6* k.setEventWidget
3059 def setEventWidget(self, event):
3060 """
3061 A hack: redirect the event to the text part of the log.
3062 """
3063 c = self.c
3064 w = event.widget
3065 w_name = c.widget_name(w)
3066 if w_name.startswith('log'):
3067 event.widget = c.frame.log.logCtrl
3068 #@+node:ekr.20180418031417.1: *6* k.traceVars
3069 def traceVars(self, event):
3071 trace = False and not g.unitTesting
3072 if not trace:
3073 return
3074 k = self
3075 char = event.char
3076 state = k.state.kind
3077 stroke = event.stroke
3078 g.trace(
3079 f"stroke: {stroke!r}, "
3080 f"char: {char!r}, "
3081 f"state: {state}, "
3082 f"state2: {k.unboundKeyAction}")
3083 #@+node:ekr.20180418031118.1: *5* 1. k.isSpecialKey
3084 def isSpecialKey(self, event):
3085 """Return True if char is a special key."""
3086 if not event:
3087 # An empty event is not an error.
3088 return False
3089 # Fix #917.
3090 if len(event.char) > 1 and not event.stroke.s:
3091 # stroke.s was cleared, but not event.char.
3092 return True
3093 return event.char in g.app.gui.ignoreChars
3094 #@+node:ekr.20180418024449.1: *5* 2. k.doKeyboardQuit
3095 def doKeyboardQuit(self, event):
3096 """
3097 A helper for k.masterKeyHandler: Handle keyboard-quit logic.
3099 return True if k.masterKeyHandler should return.
3100 """
3101 c, k = self.c, self
3102 stroke = getattr(event, 'stroke', None)
3103 if k.abortAllModesKey and stroke and stroke == k.abortAllModesKey:
3104 if getattr(c, 'screenCastController', None):
3105 c.screenCastController.quit()
3106 c.doCommandByName('keyboard-quit', event)
3107 return True
3108 return False
3109 #@+node:ekr.20180418023827.1: *5* 3. k.doDemo
3110 def doDemo(self, event):
3111 """
3112 Support the demo.py plugin.
3113 Return True if k.masterKeyHandler should return.
3114 """
3115 k = self
3116 stroke = event.stroke
3117 demo = getattr(g.app, 'demo', None)
3118 if not demo:
3119 return False
3120 #
3121 # Shortcut everything so that demo-next or demo-prev won't alter of our ivars.
3122 if k.demoNextKey and stroke == k.demoNextKey:
3123 if demo.trace:
3124 g.trace('demo-next', stroke)
3125 demo.next_command()
3126 return True
3127 if k.demoPrevKey and stroke == k.demoPrevKey:
3128 if demo.trace:
3129 g.trace('demo-prev', stroke)
3130 demo.prev_command()
3131 return True
3132 return False
3133 #@+node:ekr.20091230094319.6244: *5* 4. k.doMode & helpers
3134 def doMode(self, event):
3135 """
3136 Handle mode bindings.
3137 Return True if k.masterKeyHandler should return.
3138 """
3139 # #1757: Leo's default vim bindings make heavy use of modes.
3140 # Retain these traces!
3141 trace = 'keys' in g.app.debug
3142 k = self
3143 state = k.state.kind
3144 stroke = event.stroke
3145 if not k.inState():
3146 return False
3147 # First, honor minibuffer bindings for all except user modes.
3148 if state == 'input-shortcut':
3149 k.handleInputShortcut(event, stroke)
3150 if trace:
3151 g.trace(state, 'k.handleInputShortcut', stroke)
3152 return True
3153 if state in (
3154 'getArg', 'getFileName', 'full-command', 'auto-complete', 'vim-mode'
3155 ):
3156 if k.handleMiniBindings(event, state, stroke):
3157 if trace:
3158 g.trace(state, 'k.handleMiniBindings', stroke)
3159 return True
3160 # Second, honor general modes.
3161 if state == 'getArg':
3162 # New in Leo 5.8: Only call k.getArg for keys it can handle.
3163 if k.isPlainKey(stroke):
3164 k.getArg(event, stroke=stroke)
3165 if trace:
3166 g.trace(state, 'k.isPlain: getArg', stroke)
3167 return True
3168 if stroke.s in ('Escape', 'Tab', 'BackSpace'):
3169 k.getArg(event, stroke=stroke)
3170 if trace:
3171 g.trace(state, f"{stroke.s!r}: getArg", stroke)
3172 return True
3173 return False
3174 if state in ('getFileName', 'get-file-name'):
3175 k.getFileName(event)
3176 if trace:
3177 g.trace(state, 'k.getFileName', stroke)
3178 return True
3179 if state in ('full-command', 'auto-complete'):
3180 # Do the default state action. Calls end-command.
3181 val = k.callStateFunction(event)
3182 if val != 'do-standard-keys':
3183 handler = k.state.handler and k.state.handler.__name__ or '<no handler>'
3184 if trace:
3185 g.trace(state, 'k.callStateFunction:', handler, stroke)
3186 return True
3187 return False
3188 # Third, pass keys to user modes.
3189 d = k.masterBindingsDict.get(state)
3190 if d:
3191 assert g.isStrokeOrNone(stroke)
3192 bi = d.get(stroke)
3193 if bi:
3194 # Bound keys continue the mode.
3195 k.generalModeHandler(event,
3196 commandName=bi.commandName,
3197 func=bi.func,
3198 modeName=state,
3199 nextMode=bi.nextMode)
3200 if trace:
3201 g.trace(state, 'k.generalModeHandler', stroke)
3202 return True
3203 # Unbound keys end mode.
3204 k.endMode()
3205 return False
3206 # Fourth, call the state handler.
3207 handler = k.getStateHandler()
3208 if handler:
3209 handler(event)
3210 if trace:
3211 handler_name = handler and handler.__name__ or '<no handler>'
3212 g.trace(state, 'handler:', handler_name, stroke)
3213 return True
3214 #@+node:ekr.20061031131434.108: *6* k.callStateFunction
3215 def callStateFunction(self, event):
3216 """Call the state handler associated with this event."""
3217 k = self
3218 ch = event.char
3219 #
3220 # Defensive programming
3221 if not k.state.kind:
3222 return None
3223 if not k.state.handler:
3224 g.error('callStateFunction: no state function for', k.state.kind)
3225 return None
3226 #
3227 # Handle auto-completion before checking for unbound keys.
3228 if k.state.kind == 'auto-complete':
3229 # k.auto_completer_state_handler returns 'do-standard-keys' for control keys.
3230 val = k.state.handler(event)
3231 return val
3232 #
3233 # Ignore unbound non-ascii keys.
3234 if (
3235 k.ignore_unbound_non_ascii_keys and
3236 len(ch) == 1 and
3237 ch and ch not in ('\b', '\n', '\r', '\t') and
3238 (ord(ch) < 32 or ord(ch) > 128)
3239 ):
3240 return None
3241 #
3242 # Call the state handler.
3243 val = k.state.handler(event)
3244 return val
3245 #@+node:ekr.20061031131434.152: *6* k.handleMiniBindings
3246 def handleMiniBindings(self, event, state, stroke):
3247 """Find and execute commands bound to the event."""
3248 k = self
3249 #
3250 # Special case for bindings handled in k.getArg:
3251 if state == 'full-command' and stroke in ('Up', 'Down'):
3252 return False
3253 #
3254 # Ignore other special keys in the minibuffer.
3255 if state in ('getArg', 'full-command'):
3256 if stroke in (
3257 '\b', 'BackSpace',
3258 '\r', 'Linefeed',
3259 '\n', 'Return',
3260 '\t', 'Tab',
3261 'Escape',
3262 ):
3263 return False
3264 if k.isFKey(stroke):
3265 return False
3266 #
3267 # Ignore autocompletion state.
3268 if state.startswith('auto-'):
3269 return False
3270 #
3271 # Ignore plain key binding in the minibuffer.
3272 if not stroke or k.isPlainKey(stroke):
3273 return False
3274 #
3275 # Get the command, based on the pane.
3276 for pane in ('mini', 'all', 'text'):
3277 result = k.handleMinibufferHelper(event, pane, state, stroke)
3278 assert result in ('continue', 'found', 'ignore')
3279 if result == 'ignore':
3280 return False # Let getArg handle it.
3281 if result == 'found':
3282 # Do not call k.keyboardQuit here!
3283 return True
3284 #
3285 # No binding exists.
3286 return False
3287 #@+node:ekr.20180418114300.1: *7* k.handleMinibufferHelper
3288 def handleMinibufferHelper(self, event, pane, state, stroke):
3289 """
3290 Execute a pane binding in the minibuffer.
3291 Return 'continue', 'ignore', 'found'
3292 """
3293 c, k = self.c, self
3294 d = k.masterBindingsDict.get(pane)
3295 if not d:
3296 return 'continue'
3297 bi = d.get(stroke)
3298 if not bi:
3299 return 'continue'
3300 assert bi.stroke == stroke, f"bi: {bi} stroke: {stroke}"
3301 # Ignore the replace-string command in the minibuffer.
3302 if bi.commandName == 'replace-string' and state == 'getArg':
3303 return 'ignore'
3304 # Execute this command.
3305 if bi.commandName not in k.singleLineCommandList:
3306 k.keyboardQuit()
3307 else:
3308 c.minibufferWantsFocus()
3309 c.doCommandByName(bi.commandName, event)
3310 # Careful: the command could exit.
3311 if c.exists and not k.silentMode:
3312 # Use the state *after* executing the command.
3313 if k.state.kind:
3314 c.minibufferWantsFocus()
3315 else:
3316 c.bodyWantsFocus()
3317 return 'found'
3318 #@+node:vitalije.20170708161511.1: *6* k.handleInputShortcut
3319 def handleInputShortcut(self, event, stroke):
3320 c, k, p, u = self.c, self, self.c.p, self.c.undoer
3321 k.clearState()
3322 if p.h.startswith(('@shortcuts', '@mode')):
3323 # line of text in body
3324 w = c.frame.body.wrapper
3325 before, sel, after = w.getInsertLines()
3326 m = k._cmd_handle_input_pattern.search(sel)
3327 assert m # edit-shortcut was invoked on a malformed body line
3328 sel = f"{m.group(0)} {stroke.s}"
3329 udata = u.beforeChangeNodeContents(p)
3330 pos = w.getYScrollPosition()
3331 i = len(before)
3332 j = max(i, len(before) + len(sel) - 1)
3333 w.setAllText(before + sel + after)
3334 w.setSelectionRange(i, j, insert=j)
3335 w.setYScrollPosition(pos)
3336 u.afterChangeNodeContents(p, 'change shortcut', udata)
3337 cmdname = m.group(0).rstrip('= ')
3338 k.editShortcut_do_bind_helper(stroke, cmdname)
3339 return
3340 if p.h.startswith(('@command', '@button')):
3341 udata = u.beforeChangeNodeContents(p)
3342 cmd = p.h.split('@key', 1)[0]
3343 p.h = f"{cmd} @key={stroke.s}"
3344 u.afterChangeNodeContents(p, 'change shortcut', udata)
3345 try:
3346 cmdname = cmd.split(' ', 1)[1].strip()
3347 k.editShortcut_do_bind_helper(stroke, cmdname)
3348 except IndexError:
3349 pass
3350 return
3351 # this should never happen
3352 g.error('not in settings node shortcut')
3353 #@+node:vitalije.20170709151653.1: *7* k.isInShortcutBodyLine
3354 _cmd_handle_input_pattern = re.compile(r'[A-Za-z0-9_\-]+\s*=')
3356 def isInShortcutBodyLine(self):
3357 c, k = self.c, self
3358 p = c.p
3359 if p.h.startswith(('@shortcuts', '@mode')):
3360 # line of text in body
3361 w = c.frame.body
3362 before, sel, after = w.getInsertLines()
3363 m = k._cmd_handle_input_pattern.search(sel)
3364 return bool(m)
3365 return p.h.startswith(('@command', '@button'))
3366 #@+node:vitalije.20170709151658.1: *7* k.isEditShortcutSensible
3367 def isEditShortcutSensible(self):
3368 c, k = self.c, self
3369 p = c.p
3370 return p.h.startswith(('@command', '@button')) or k.isInShortcutBodyLine()
3371 #@+node:vitalije.20170709202924.1: *7* k.editShortcut_do_bind_helper
3372 def editShortcut_do_bind_helper(self, stroke, cmdname):
3373 c, k = self.c, self
3374 cmdfunc = c.commandsDict.get(cmdname)
3375 if cmdfunc:
3376 k.bindKey('all', stroke, cmdfunc, cmdname)
3377 g.es('bound', stroke, 'to command', cmdname)
3378 #@+node:ekr.20180418025241.1: *5* 5. k.doVim
3379 def doVim(self, event):
3380 """
3381 Handle vim mode.
3382 Return True if k.masterKeyHandler should return.
3383 """
3384 trace = all(z in g.app.debug for z in ('keys', 'verbose'))
3385 c = self.c
3386 if c.vim_mode and c.vimCommands:
3387 # The "acceptance methods" in leoVim.py return True
3388 # if vim node has completely handled the key.
3389 # Otherwise, processing in k.masterKeyHandler continues.
3390 ok = c.vimCommands.do_key(event)
3391 if trace:
3392 g.trace('do_key returns', ok, repr(event and event.stroke))
3393 return ok
3394 return False
3395 #@+node:ekr.20180418033838.1: *5* 6. k.doBinding & helpers
3396 def doBinding(self, event):
3397 """
3398 Attempt to find a binding for the event's stroke.
3399 If found, execute the command and return True
3400 Otherwise, return False
3401 """
3402 trace = 'keys' in g.app.debug
3403 c, k = self.c, self
3404 #
3405 # Experimental special case:
3406 # Inserting a '.' always invokes the auto-completer.
3407 # The auto-completer just inserts a '.' if it isn't enabled.
3408 stroke = event.stroke
3409 if (
3410 stroke.s == '.'
3411 and k.isPlainKey(stroke)
3412 and self.unboundKeyAction in ('insert', 'overwrite')
3413 ):
3414 c.doCommandByName('auto-complete', event)
3415 return True
3416 #
3417 # Use getPaneBindings for *all* keys.
3418 bi = k.getPaneBinding(event)
3419 #
3420 # #327: Ignore killed bindings.
3421 if bi and bi.commandName in k.killedBindings:
3422 return False
3423 #
3424 # Execute the command if the binding exists.
3425 if bi:
3426 # A superb trace. !s gives shorter trace.
3427 if trace:
3428 g.trace(f"{event.stroke!s} {bi.commandName}")
3429 c.doCommandByName(bi.commandName, event)
3430 return True
3431 #
3432 # No binding exists.
3433 return False
3434 #@+node:ekr.20091230094319.6240: *6* k.getPaneBinding & helper
3435 def getPaneBinding(self, event):
3436 c, k, state = self.c, self, self.unboundKeyAction
3437 stroke, w = event.stroke, event.w
3438 if not g.assert_is(stroke, g.KeyStroke):
3439 return None
3440 # #1757: Always insert plain keys in the body.
3441 # Valid because mode bindings have already been handled.
3442 if (
3443 k.isPlainKey(stroke)
3444 and w == c.frame.body.widget
3445 and state in ('insert', 'overwrite')
3446 ):
3447 return None
3448 for key, name in (
3449 # Order here is similar to bindtags order.
3450 ('command', None),
3451 ('insert', None),
3452 ('overwrite', None),
3453 ('button', None),
3454 ('body', 'body'),
3455 ('text', 'head'), # Important: text bindings in head before tree bindings.
3456 ('tree', 'head'),
3457 ('tree', 'canvas'),
3458 ('log', 'log'),
3459 ('text', 'log'),
3460 ('text', None),
3461 ('all', None),
3462 ):
3463 bi = k.getBindingHelper(key, name, stroke, w)
3464 if bi:
3465 return bi
3466 return None
3467 #@+node:ekr.20180418105228.1: *7* getPaneBindingHelper
3468 def getBindingHelper(self, key, name, stroke, w):
3469 """Find a binding for the widget with the given name."""
3470 c, k = self.c, self
3471 #
3472 # Return if the pane's name doesn't match the event's widget.
3473 state = k.unboundKeyAction
3474 w_name = c.widget_name(w)
3475 pane_matches = (
3476 name and w_name.startswith(name) or
3477 key in ('command', 'insert', 'overwrite') and state == key or
3478 key in ('text', 'all') and g.isTextWrapper(w) or
3479 key in ('button', 'all')
3480 )
3481 if not pane_matches:
3482 return None
3483 #
3484 # Return if there is no binding at all.
3485 d = k.masterBindingsDict.get(key, {})
3486 if not d:
3487 return None
3488 bi = d.get(stroke)
3489 if not bi:
3490 return None
3491 #
3492 # Ignore previous/next-line commands while editing headlines.
3493 if (
3494 key == 'text' and
3495 name == 'head' and
3496 bi.commandName in ('previous-line', 'next-line')
3497 ):
3498 return None
3499 #
3500 # The binding has been found.
3501 return bi
3502 #@+node:ekr.20160409035115.1: *6* k.searchTree
3503 def searchTree(self, char):
3504 """Search all visible nodes for a headline starting with stroke."""
3505 if not char:
3506 return
3507 c = self.c
3508 if not c.config.getBool('plain-key-outline-search'):
3509 return
3511 def match(p):
3512 """Return True if p contains char."""
3513 s = p.h.lower() if char.islower() else p.h
3514 return s.find(char) > -1
3516 # Start at c.p, then retry everywhere.
3518 for p in (c.p, c.rootPosition()):
3519 p = p.copy()
3520 if p == c.p and match(p):
3521 p.moveToVisNext(c)
3522 while p:
3523 if match(p):
3524 c.selectPosition(p)
3525 c.redraw()
3526 return
3527 p.moveToVisNext(c)
3529 # Too confusing for the user.
3530 # re_pat = re.compile(r'^@(\w)+[ \t](.+)')
3532 # def match(p, pattern):
3533 # s = p.h.lower()
3534 # if pattern:
3535 # m = pattern.search(s)
3536 # found = (s.startswith(char) or
3537 # m and m.group(2).lower().startswith(char))
3538 # else:
3539 # found = s.find(char) > -1
3540 # if found:
3541 # c.selectPosition(p)
3542 # c.redraw()
3543 # return found
3544 #@+node:ekr.20061031170011.3: *3* k.Minibuffer
3545 # These may be overridden, but this code is now gui-independent.
3546 #@+node:ekr.20061031170011.9: *4* k.extendLabel
3547 def extendLabel(self, s, select=False, protect=False):
3549 c, k, w = self.c, self, self.w
3550 if not (w and s):
3551 return
3552 c.widgetWantsFocusNow(w)
3553 w.insert('end', s)
3554 if select:
3555 i, j = k.getEditableTextRange()
3556 w.setSelectionRange(i, j, insert=j)
3557 if protect:
3558 k.protectLabel()
3559 #@+node:ekr.20061031170011.13: *4* k.getEditableTextRange
3560 def getEditableTextRange(self):
3561 k, w = self, self.w
3562 s = w.getAllText()
3563 i = len(k.mb_prefix)
3564 j = len(s)
3565 return i, j
3566 #@+node:ekr.20061031170011.5: *4* k.getLabel
3567 def getLabel(self, ignorePrompt=False):
3568 k, w = self, self.w
3569 if not w:
3570 return ''
3571 s = w.getAllText()
3572 if ignorePrompt:
3573 return s[len(k.mb_prefix) :]
3574 return s or ''
3575 #@+node:ekr.20080408060320.791: *4* k.killLine
3576 def killLine(self, protect=True):
3577 k = self
3578 w = k.w
3579 s = w.getAllText()
3580 s = s[: len(k.mb_prefix)]
3581 w.setAllText(s)
3582 n = len(s)
3583 w.setSelectionRange(n, n, insert=n)
3584 if protect:
3585 k.mb_prefix = s
3586 #@+node:ekr.20061031131434.135: *4* k.minibufferWantsFocus
3587 # def minibufferWantsFocus(self):
3588 # c = self.c
3589 # c.widgetWantsFocus(c.miniBufferWidget)
3590 #@+node:ekr.20061031170011.6: *4* k.protectLabel
3591 def protectLabel(self):
3592 k, w = self, self.w
3593 if not w:
3594 return
3595 k.mb_prefix = w.getAllText()
3596 #@+node:ekr.20061031170011.7: *4* k.resetLabel
3597 def resetLabel(self):
3598 """Reset the minibuffer label."""
3599 k = self
3600 c, w = k.c, k.w
3601 k.setLabelGrey('')
3602 k.mb_prefix = ''
3603 if w:
3604 w.setSelectionRange(0, 0, insert=0)
3605 state = k.unboundKeyAction
3606 if c.vim_mode and c.vimCommands:
3607 c.vimCommands.show_status()
3608 else:
3609 k.setLabelBlue(label=f"{state.capitalize()} State")
3610 #@+node:ekr.20080408060320.790: *4* k.selectAll
3611 def selectAll(self):
3612 """Select all the user-editable text of the minibuffer."""
3613 w = self.w
3614 i, j = self.getEditableTextRange()
3615 w.setSelectionRange(i, j, insert=j)
3616 #@+node:ekr.20061031170011.8: *4* k.setLabel
3617 def setLabel(self, s, protect=False):
3618 """Set the label of the minibuffer."""
3619 c, k, w = self.c, self, self.w
3620 if w:
3621 # Support for the curses gui.
3622 if hasattr(g.app.gui, 'set_minibuffer_label'):
3623 g.app.gui.set_minibuffer_label(c, s)
3624 w.setAllText(s)
3625 n = len(s)
3626 w.setSelectionRange(n, n, insert=n)
3627 if protect:
3628 k.mb_prefix = s
3629 #@+node:ekr.20061031170011.10: *4* k.setLabelBlue
3630 def setLabelBlue(self, label, protect=True):
3631 """Set the minibuffer label."""
3632 k, w = self, self.w
3633 if hasattr(g.app.gui, 'set_minibuffer_label'):
3634 g.app.gui.set_minibuffer_label(self.c, label)
3635 elif w:
3636 w.setStyleClass('') # normal state, not warning or error
3637 if label is not None:
3638 k.setLabel(label, protect=protect)
3639 #@+node:ekr.20061031170011.11: *4* k.setLabelGrey
3640 def setLabelGrey(self, label=None):
3641 k, w = self, self.w
3642 if not w:
3643 return
3644 w.setStyleClass('minibuffer_warning')
3645 if label is not None:
3646 k.setLabel(label)
3648 setLabelGray = setLabelGrey
3649 #@+node:ekr.20080510153327.2: *4* k.setLabelRed
3650 def setLabelRed(self, label=None, protect=False):
3651 k, w = self, self.w
3652 if not w:
3653 return
3654 w.setStyleClass('minibuffer_error')
3655 if label is not None:
3656 k.setLabel(label, protect)
3657 #@+node:ekr.20140822051549.18298: *4* k.setStatusLabel
3658 def setStatusLabel(self, s):
3659 """
3660 Set the label to s.
3662 Use k.setStatusLabel, not k.setLael, to report the status of a Leo
3663 command. This allows the option to use g.es instead of the minibuffer
3664 to report status.
3665 """
3666 k = self
3667 k.setLabel(s, protect=False)
3668 #@+node:ekr.20061031170011.12: *4* k.updateLabel
3669 def updateLabel(self, event):
3670 """
3671 Mimic what would happen with the keyboard and a Text editor
3672 instead of plain accumulation.
3673 """
3674 c, k, w = self.c, self, self.w
3675 if not event:
3676 return
3677 ch, stroke = event.char, event.stroke
3678 if ch in "\n\r":
3679 return
3680 if stroke and not k.isPlainKey(stroke):
3681 return # #2041.
3682 c.widgetWantsFocusNow(w)
3683 i, j = w.getSelectionRange()
3684 ins = w.getInsertPoint()
3685 if i != j:
3686 w.delete(i, j)
3687 if ch == '\b':
3688 s = w.getAllText()
3689 if len(s) > len(k.mb_prefix):
3690 w.delete(i - 1)
3691 i -= 1
3692 else:
3693 w.insert(ins, ch)
3694 i = ins + 1
3695 #@+node:ekr.20120208064440.10190: *3* k.Modes
3696 #@+node:ekr.20061031131434.100: *4* k.addModeCommands (enterModeCallback)
3697 def addModeCommands(self):
3698 """Add commands created by @mode settings to c.commandsDict."""
3699 c, k = self.c, self
3700 d = g.app.config.modeCommandsDict # Keys are command names: enter-x-mode.
3701 # Create the callback functions and update c.commandsDict.
3702 for key in d.keys():
3703 # pylint: disable=cell-var-from-loop
3705 def enterModeCallback(event=None, name=key):
3706 k.enterNamedMode(event, name)
3708 c.commandsDict[key] = enterModeCallback
3709 #@+node:ekr.20061031131434.157: *4* k.badMode
3710 def badMode(self, modeName):
3711 k = self
3712 k.clearState()
3713 if modeName.endswith('-mode'):
3714 modeName = modeName[:-5]
3715 k.setLabelGrey(f"@mode {modeName} is not defined (or is empty)")
3716 #@+node:ekr.20061031131434.158: *4* k.createModeBindings
3717 def createModeBindings(self, modeName, d, w):
3718 """Create mode bindings for the named mode using dictionary d for w, a text widget."""
3719 c, k = self.c, self
3720 assert d.name().endswith('-mode')
3721 for commandName in d.keys():
3722 if commandName in ('*entry-commands*', '*command-prompt*'):
3723 # These are special-purpose dictionary entries.
3724 continue
3725 func = c.commandsDict.get(commandName)
3726 if not func:
3727 g.es_print('no such command:', commandName, 'Referenced from', modeName)
3728 continue
3729 aList = d.get(commandName, [])
3730 for bi in aList:
3731 stroke = bi.stroke
3732 # Important: bi.val is canonicalized.
3733 if stroke and stroke not in ('None', 'none', None):
3734 assert g.isStroke(stroke)
3735 k.makeMasterGuiBinding(stroke)
3736 # Create the entry for the mode in k.masterBindingsDict.
3737 # Important: this is similar, but not the same as k.bindKeyToDict.
3738 # Thus, we should **not** call k.bindKey here!
3739 d2 = k.masterBindingsDict.get(modeName, {})
3740 d2[stroke] = g.BindingInfo(
3741 kind=f"mode<{modeName}>",
3742 commandName=commandName,
3743 func=func,
3744 nextMode=bi.nextMode,
3745 stroke=stroke)
3746 k.masterBindingsDict[modeName] = d2
3747 #@+node:ekr.20120208064440.10179: *4* k.endMode
3748 def endMode(self):
3749 c, k = self.c, self
3750 w = g.app.gui.get_focus(c)
3751 if w:
3752 c.frame.log.deleteTab('Mode') # Changes focus to the body pane
3753 k.inputModeName = None
3754 k.clearState()
3755 k.resetLabel()
3756 k.showStateAndMode() # Restores focus.
3757 if w:
3758 c.widgetWantsFocusNow(w)
3759 #@+node:ekr.20061031131434.160: *4* k.enterNamedMode
3760 def enterNamedMode(self, event, commandName):
3761 c, k = self.c, self
3762 modeName = commandName[6:]
3763 c.inCommand = False # Allow inner commands in the mode.
3764 k.generalModeHandler(event, modeName=modeName)
3765 #@+node:ekr.20061031131434.161: *4* k.exitNamedMode
3766 @cmd('exit-named-mode')
3767 def exitNamedMode(self, event=None):
3768 """Exit an input mode."""
3769 k = self
3770 if k.inState():
3771 k.endMode()
3772 k.showStateAndMode()
3773 #@+node:ekr.20120208064440.10199: *4* k.generalModeHandler
3774 def generalModeHandler(self, event,
3775 commandName=None,
3776 func=None,
3777 modeName=None,
3778 nextMode=None,
3779 prompt=None
3780 ):
3781 """Handle a mode defined by an @mode node in leoSettings.leo."""
3782 c, k = self.c, self
3783 state = k.getState(modeName)
3784 if state == 0:
3785 k.inputModeName = modeName
3786 k.modePrompt = prompt or modeName
3787 k.modeWidget = event and event.widget
3788 k.setState(modeName, 1, handler=k.generalModeHandler)
3789 self.initMode(event, modeName)
3790 # Careful: k.initMode can execute commands that will destroy a commander.
3791 if g.app.quitting or not c.exists:
3792 return
3793 if not k.silentMode:
3794 if c.config.getBool('showHelpWhenEnteringModes'):
3795 k.modeHelp(event)
3796 else:
3797 c.frame.log.hideTab('Mode')
3798 elif not func:
3799 g.trace('No func: improper key binding')
3800 else:
3801 if commandName == 'mode-help':
3802 func(event)
3803 else:
3804 self.endMode()
3805 # New in 4.4.1 b1: pass an event describing the original widget.
3806 if event:
3807 event.w = event.widget = k.modeWidget
3808 else:
3809 event = g.app.gui.create_key_event(c, w=k.modeWidget)
3810 func(event)
3811 if g.app.quitting or not c.exists:
3812 pass
3813 elif nextMode in (None, 'none'):
3814 # Do *not* clear k.inputModeName or the focus here.
3815 # func may have put us in *another* mode.
3816 pass
3817 elif nextMode == 'same':
3818 silent = k.silentMode
3819 k.setState(modeName, 1, handler=k.generalModeHandler)
3820 self.reinitMode(modeName) # Re-enter this mode.
3821 k.silentMode = silent
3822 else:
3823 k.silentMode = False # All silent modes must do --> set-silent-mode.
3824 self.initMode(event, nextMode) # Enter another mode.
3825 #@+node:ekr.20061031131434.163: *4* k.initMode
3826 def initMode(self, event, modeName):
3828 c, k = self.c, self
3829 if not modeName:
3830 g.trace('oops: no modeName')
3831 return
3832 d = g.app.config.modeCommandsDict.get('enter-' + modeName)
3833 if not d:
3834 self.badMode(modeName)
3835 return
3836 k.modeBindingsDict = d
3837 bi = d.get('*command-prompt*')
3838 prompt = bi.kind if bi else modeName
3839 k.inputModeName = modeName
3840 k.silentMode = False
3841 aList = d.get('*entry-commands*', [])
3842 if aList:
3843 for bi in aList:
3844 commandName = bi.commandName
3845 k.simulateCommand(commandName)
3846 # Careful, the command can kill the commander.
3847 if g.app.quitting or not c.exists:
3848 return
3849 # New in Leo 4.5: a startup command can immediately transfer to another mode.
3850 if commandName.startswith('enter-'):
3851 return
3852 # Create bindings after we know whether we are in silent mode.
3853 w = k.modeWidget if k.silentMode else k.w
3854 k.createModeBindings(modeName, d, w)
3855 k.showStateAndMode(prompt=prompt)
3856 #@+node:ekr.20061031131434.165: *4* k.modeHelp & helper
3857 @cmd('mode-help')
3858 def modeHelp(self, event):
3859 """
3860 The mode-help command.
3862 A possible convention would be to bind <Tab> to this command in most modes,
3863 by analogy with tab completion.
3864 """
3865 c, k = self.c, self
3866 c.endEditing()
3867 if k.inputModeName:
3868 d = g.app.config.modeCommandsDict.get('enter-' + k.inputModeName)
3869 k.modeHelpHelper(d)
3870 if not k.silentMode:
3871 c.minibufferWantsFocus()
3872 #@+node:ekr.20061031131434.166: *5* modeHelpHelper
3873 def modeHelpHelper(self, d):
3874 c, k = self.c, self
3875 tabName = 'Mode'
3876 c.frame.log.clearTab(tabName)
3877 data, n = [], 0
3878 for key in sorted(d.keys()):
3879 if key in ('*entry-commands*', '*command-prompt*'):
3880 pass
3881 else:
3882 aList = d.get(key)
3883 for bi in aList:
3884 stroke = bi.stroke
3885 if stroke not in (None, 'None'):
3886 s1 = key
3887 s2 = k.prettyPrintKey(stroke)
3888 n = max(n, len(s1))
3889 data.append((s1, s2),)
3890 data.sort()
3891 modeName = k.inputModeName.replace('-', ' ')
3892 if modeName.endswith('mode'):
3893 modeName = modeName[:-4].strip()
3894 prompt = d.get('*command-prompt*')
3895 if prompt:
3896 g.es('', f"{prompt.kind.strip()}\n\n", tabName=tabName)
3897 else:
3898 g.es('', f"{modeName} mode\n\n", tabName=tabName)
3899 # This isn't perfect in variable-width fonts.
3900 for s1, s2 in data:
3901 g.es('', '%*s %s' % (n, s1, s2), tabName=tabName)
3902 #@+node:ekr.20061031131434.164: *4* k.reinitMode
3903 def reinitMode(self, modeName):
3904 k = self
3905 d = k.modeBindingsDict
3906 k.inputModeName = modeName
3907 w = k.modeWidget if k.silentMode else k.w
3908 k.createModeBindings(modeName, d, w)
3909 if k.silentMode:
3910 k.showStateAndMode()
3911 else:
3912 # Do not set the status line here.
3913 k.setLabelBlue(modeName + ': ') # ,protect=True)
3914 #@+node:ekr.20061031131434.181: *3* k.Shortcuts & bindings
3915 #@+node:ekr.20061031131434.176: *4* k.computeInverseBindingDict
3916 def computeInverseBindingDict(self):
3917 k = self
3918 d = {} # keys are minibuffer command names, values are shortcuts.
3919 for stroke in k.bindingsDict:
3920 assert g.isStroke(stroke), repr(stroke)
3921 aList = k.bindingsDict.get(stroke, [])
3922 for bi in aList:
3923 shortcutList = k.bindingsDict.get(bi.commandName, [])
3924 bi_list = k.bindingsDict.get(
3925 stroke, g.BindingInfo(kind='dummy', pane='all'))
3926 # Important: only bi.pane is required below.
3927 for bi in bi_list:
3928 pane = f"{bi.pane}:"
3929 data = (pane, stroke)
3930 if data not in shortcutList:
3931 shortcutList.append(data)
3932 d[bi.commandName] = shortcutList
3933 return d
3934 #@+node:ekr.20061031131434.179: *4* k.getShortcutForCommandName
3935 def getStrokeForCommandName(self, commandName):
3936 c, k = self.c, self
3937 command = c.commandsDict.get(commandName)
3938 if command:
3939 for stroke, aList in k.bindingsDict.items():
3940 for bi in aList:
3941 if bi.commandName == commandName:
3942 return stroke
3943 return None
3944 #@+node:ekr.20090518072506.8494: *4* k.isFKey
3945 def isFKey(self, stroke):
3946 # k = self
3947 if not stroke:
3948 return False
3949 assert isinstance(stroke, str) or g.isStroke(stroke)
3950 s = stroke.s if g.isStroke(stroke) else stroke
3951 s = s.lower()
3952 return s.startswith('f') and len(s) <= 3 and s[1:].isdigit()
3953 #@+node:ekr.20061031131434.182: *4* k.isPlainKey
3954 def isPlainKey(self, stroke):
3955 """Return true if the shortcut refers to a plain (non-Alt,non-Ctl) key."""
3956 if not stroke:
3957 return False
3958 if not g.isStroke(stroke):
3959 # Happens during unit tests.
3960 stroke = g.KeyStroke(stroke)
3961 #
3962 # altgr combos (Alt+Ctrl) are always plain keys
3963 # g.KeyStroke does not handle this, because it has no "c" ivar.
3964 #
3965 if stroke.isAltCtrl() and not self.enable_alt_ctrl_bindings:
3966 return True
3967 return stroke.isPlainKey()
3968 #@+node:ekr.20061031131434.191: *4* k.prettyPrintKey
3969 def prettyPrintKey(self, stroke, brief=False):
3971 if not stroke:
3972 return ''
3973 if not g.assert_is(stroke, g.KeyStroke):
3974 return stroke
3975 return stroke.prettyPrint()
3976 #@+node:ekr.20110606004638.16929: *4* k.stroke2char
3977 def stroke2char(self, stroke):
3978 """
3979 Convert a stroke to an (insertable) char.
3980 This method allows Leo to use strokes everywhere.
3981 """
3982 if not stroke:
3983 return ''
3984 if not g.isStroke(stroke):
3985 # vim commands pass a plain key.
3986 stroke = g.KeyStroke(stroke)
3987 return stroke.toInsertableChar()
3988 #@+node:ekr.20061031131434.193: *3* k.States
3989 #@+node:ekr.20061031131434.194: *4* k.clearState
3990 def clearState(self):
3991 """Clear the key handler state."""
3992 k = self
3993 k.state.kind = None
3994 k.state.n = None
3995 k.state.handler = None
3996 #@+node:ekr.20061031131434.196: *4* k.getState
3997 def getState(self, kind):
3998 k = self
3999 val = k.state.n if k.state.kind == kind else 0
4000 return val
4001 #@+node:ekr.20061031131434.195: *4* k.getStateHandler
4002 def getStateHandler(self):
4003 return self.state.handler
4004 #@+node:ekr.20061031131434.197: *4* k.getStateKind
4005 def getStateKind(self):
4006 return self.state.kind
4007 #@+node:ekr.20061031131434.198: *4* k.inState
4008 def inState(self, kind=None):
4009 k = self
4010 if kind:
4011 return k.state.kind == kind and k.state.n is not None
4012 return k.state.kind and k.state.n is not None
4013 #@+node:ekr.20080511122507.4: *4* k.setDefaultInputState
4014 def setDefaultInputState(self):
4015 k = self
4016 state = k.defaultUnboundKeyAction
4017 k.setInputState(state)
4018 #@+node:ekr.20110209093958.15411: *4* k.setEditingState
4019 def setEditingState(self):
4020 k = self
4021 state = k.defaultEditingAction
4022 k.setInputState(state)
4023 #@+node:ekr.20061031131434.133: *4* k.setInputState
4024 def setInputState(self, state, set_border=False):
4025 k = self
4026 k.unboundKeyAction = state
4027 #@+node:ekr.20061031131434.199: *4* k.setState
4028 def setState(self, kind, n, handler=None):
4030 k = self
4031 if kind and n is not None:
4032 k.state.kind = kind
4033 k.state.n = n
4034 if handler:
4035 k.state.handler = handler
4036 else:
4037 k.clearState()
4038 # k.showStateAndMode()
4039 #@+node:ekr.20061031131434.192: *4* k.showStateAndMode
4040 def showStateAndMode(self, w=None, prompt=None, setFocus=True):
4041 """Show the state and mode at the start of the minibuffer."""
4042 c, k = self.c, self
4043 state = k.unboundKeyAction
4044 mode = k.getStateKind()
4045 if not g.app.gui:
4046 return
4047 if not w:
4048 if hasattr(g.app.gui, 'set_minibuffer_label'):
4049 pass # we don't need w
4050 else:
4051 w = g.app.gui.get_focus(c)
4052 if not w:
4053 return
4054 isText = g.isTextWrapper(w)
4055 # This fixes a problem with the tk gui plugin.
4056 if mode and mode.lower().startswith('isearch'):
4057 return
4058 wname = g.app.gui.widget_name(w).lower()
4059 # Get the wrapper for the headline widget.
4060 if wname.startswith('head'):
4061 if hasattr(c.frame.tree, 'getWrapper'):
4062 if hasattr(w, 'widget'):
4063 w2 = w.widget
4064 else:
4065 w2 = w
4066 w = c.frame.tree.getWrapper(w2, item=None)
4067 isText = bool(w) # A benign hack.
4068 if mode:
4069 if mode in ('getArg', 'getFileName', 'full-command'):
4070 s = None
4071 elif prompt:
4072 s = prompt
4073 else:
4074 mode = mode.strip()
4075 if mode.endswith('-mode'):
4076 mode = mode[:-5]
4077 s = f"{mode.capitalize()} Mode"
4078 elif c.vim_mode and c.vimCommands:
4079 c.vimCommands.show_status()
4080 return
4081 else:
4082 s = f"{state.capitalize()} State"
4083 if c.editCommands.extendMode:
4084 s = s + ' (Extend Mode)'
4085 if s:
4086 k.setLabelBlue(s)
4087 if w and isText:
4088 # k.showStateColors(inOutline,w)
4089 k.showStateCursor(state, w)
4090 # 2015/07/11: reset the status line.
4091 if hasattr(c.frame.tree, 'set_status_line'):
4092 c.frame.tree.set_status_line(c.p)
4093 #@+node:ekr.20110202111105.15439: *4* k.showStateCursor
4094 def showStateCursor(self, state, w):
4095 pass
4096 #@-others
4097#@+node:ekr.20120208064440.10150: ** class ModeInfo
4098class ModeInfo:
4100 def __repr__(self):
4101 return f"<ModeInfo {self.name}>"
4103 __str__ = __repr__
4104 #@+others
4105 #@+node:ekr.20120208064440.10193: *3* mode_i. ctor
4106 def __init__(self, c, name, aList):
4108 self.c = c
4109 self.d = {} # The bindings in effect for this mode.
4110 # Keys are names of (valid) command names, values are BindingInfo objects.
4111 self.entryCommands = []
4112 # A list of BindingInfo objects.
4113 self.k = c.k
4114 self.name = self.computeModeName(name)
4115 self.prompt = self.computeModePrompt(self.name)
4116 self.init(name, aList)
4117 #@+node:ekr.20120208064440.10152: *3* mode_i.computeModeName
4118 def computeModeName(self, name):
4119 s = name.strip().lower()
4120 j = s.find(' ')
4121 if j > -1:
4122 s = s[:j]
4123 if s.endswith('mode'):
4124 s = s[:-4].strip()
4125 if s.endswith('-'):
4126 s = s[:-1]
4127 i = s.find('::')
4128 if i > -1:
4129 # The actual mode name is everything up to the "::"
4130 # The prompt is everything after the prompt.
4131 s = s[:i]
4132 return s + '-mode'
4133 #@+node:ekr.20120208064440.10156: *3* mode_i.computeModePrompt
4134 def computeModePrompt(self, name):
4135 assert name == self.name
4136 s = 'enter-' + name.replace(' ', '-')
4137 i = s.find('::')
4138 if i > -1:
4139 # The prompt is everything after the '::'
4140 prompt = s[i + 2 :].strip()
4141 else:
4142 prompt = s
4143 return prompt
4144 #@+node:ekr.20120208064440.10160: *3* mode_i.createModeBindings
4145 def createModeBindings(self, w):
4146 """Create mode bindings for w, a text widget."""
4147 c, d, k, modeName = self.c, self.d, self.k, self.name
4148 for commandName in d:
4149 func = c.commandsDict.get(commandName)
4150 if not func:
4151 g.es_print(f"no such command: {commandName} Referenced from {modeName}")
4152 continue
4153 aList = d.get(commandName, [])
4154 for bi in aList:
4155 stroke = bi.stroke
4156 # Important: bi.val is canonicalized.
4157 if stroke and stroke not in ('None', 'none', None):
4158 assert g.isStroke(stroke)
4159 k.makeMasterGuiBinding(stroke)
4160 # Create the entry for the mode in k.masterBindingsDict.
4161 # Important: this is similar, but not the same as k.bindKeyToDict.
4162 # Thus, we should **not** call k.bindKey here!
4163 d2 = k.masterBindingsDict.get(modeName, {})
4164 d2[stroke] = g.BindingInfo(
4165 kind=f"mode<{modeName}>",
4166 commandName=commandName,
4167 func=func,
4168 nextMode=bi.nextMode,
4169 stroke=stroke)
4170 k.masterBindingsDict[modeName] = d2
4171 #@+node:ekr.20120208064440.10195: *3* mode_i.createModeCommand
4172 def createModeCommand(self):
4173 c = self.c
4174 key = 'enter-' + self.name.replace(' ', '-')
4176 def enterModeCallback(event=None, self=self):
4177 self.enterMode()
4179 c.commandsDict[key] = f = enterModeCallback
4180 g.trace('(ModeInfo)', f.__name__, key,
4181 'len(c.commandsDict.keys())', len(list(c.commandsDict.keys())))
4182 #@+node:ekr.20120208064440.10180: *3* mode_i.enterMode
4183 def enterMode(self):
4185 c, k = self.c, self.k
4186 c.inCommand = False # Allow inner commands in the mode.
4187 event = None
4188 k.generalModeHandler(event, modeName=self.name)
4189 #@+node:ekr.20120208064440.10153: *3* mode_i.init
4190 def init(self, name, dataList):
4191 """aList is a list of tuples (commandName,bi)."""
4192 c, d, modeName = self.c, self.d, self.name
4193 for name, bi in dataList:
4194 if not name:
4195 # An entry command: put it in the special *entry-commands* key.
4196 self.entryCommands.append(bi)
4197 elif bi is not None:
4198 # A regular shortcut.
4199 bi.pane = modeName
4200 aList = d.get(name, [])
4201 # Important: use previous bindings if possible.
4202 key2, aList2 = c.config.getShortcut(name)
4203 aList3 = [z for z in aList2 if z.pane != modeName]
4204 if aList3:
4205 aList.extend(aList3)
4206 aList.append(bi)
4207 d[name] = aList
4208 #@+node:ekr.20120208064440.10158: *3* mode_i.initMode
4209 def initMode(self):
4211 c, k = self.c, self.c.k
4212 k.inputModeName = self.name
4213 k.silentMode = False
4214 for bi in self.entryCommands:
4215 commandName = bi.commandName
4216 k.simulateCommand(commandName)
4217 # Careful, the command can kill the commander.
4218 if g.app.quitting or not c.exists:
4219 return
4220 # New in Leo 4.5: a startup command can immediately transfer to another mode.
4221 if commandName.startswith('enter-'):
4222 return
4223 # Create bindings after we know whether we are in silent mode.
4224 w = k.modeWidget if k.silentMode else k.w
4225 k.createModeBindings(self.name, self.d, w)
4226 k.showStateAndMode(prompt=self.name)
4227 #@-others
4228#@-others
4229#@@language python
4230#@@tabwidth -4
4231#@@pagewidth 70
4232#@-leo