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