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