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