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

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. 

168 

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() 

193 

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) 

240 

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() 

247 

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() 

253 

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() 

259 

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() 

265 

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() 

271 

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) 

310 

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: 

320 

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) 

344 

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) 

350 

351 def beginTabName(self, word: str) -> None: 

352 self.setTabName('AutoComplete ' + word) 

353 

354 def clearTabName(self) -> None: 

355 self.setTabName('AutoComplete ') 

356 

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]) 

362 

363 # Underscores are not valid in Pmw tab names! 

364 

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. 

374 

375 Evaluates s using eval(s,namespace) 

376 

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. 

380 

381 For class instances, class members are are also considered.) 

382 

383 **Warning**: this can still invoke arbitrary C code, if an object 

384 with a __getattr__ hook is evaluated. 

385 

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 

564 

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. 

674 

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 [] 

682 

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 [] 

691 

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]: 

702 

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: 

869 

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: 

895 

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. 

928 

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. 

975 

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: 

984 

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. 

1044 

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 

1054 

1055 For simple dynamic code completion engines. 

1056 """ 

1057 

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) 

1259 

1260 def get_label(self) -> str: 

1261 """Return the label, not including the prompt.""" 

1262 return self.c.k.getLabel(ignorePrompt=True) 

1263 

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. 

1285 

1286 k.getArg maps to ga.get_arg, which gets arguments in the minibuffer. 

1287 

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()) 

1322 

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()) 

1336 

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. 

1368 

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. 

1435 

1436 if 0: 

1437 

1438 def extend_label(self, s: str) -> None: 

1439 """Extend the label by s.""" 

1440 self.c.k.extendLabel(s, select=False, protect=False) 

1441 

1442 def get_label(self) -> str: 

1443 """Return the label, not including the prompt.""" 

1444 return self.c.k.getLabel(ignorePrompt=True) 

1445 

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. 

1469 

1470 Ctrl-G will abort this processing at any time. 

1471 

1472 All commands needing user input call k.getArg, which just calls ga.get_arg. 

1473 

1474 The arguments to ga.get_arg are as follows: 

1475 

1476 event: The event passed to the command. 

1477 

1478 returnKind=None: A string. 

1479 returnState=None, An int. 

1480 handler=None, A function. 

1481 

1482 When the argument is complete, ga.do_end does:: 

1483 

1484 if kind: k.setState(kind,n,handler) 

1485 

1486 tabList=[]: A list of possible completions. 

1487 

1488 completion=True: True if completions are enabled. 

1489 

1490 oneCharacter=False: True if k.arg should be a single character. 

1491 

1492 stroke=None: The incoming key stroke. 

1493 

1494 useMinibuffer=True: True: put focus in the minibuffer while accumulating arguments. 

1495 False allows sort-lines, for example, to show the selection range. 

1496 

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: 

1526 

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: 

1734 

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) 

1742 

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: 

1990 

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. 

2059 

2060 No actual gui bindings are made: only entries in k.masterBindingsDict 

2061 and k.bindingsDict. 

2062 

2063 tag gives the source of the binding. 

2064 

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 

2109 

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: 

2162 

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. 

2188 

2189 def openWithCallback(event: Event=None, c: Cmdr=c, d: Dict=d) -> None: 

2190 return c.openWith(d=d) 

2191 

2192 # Use k.registerCommand to set the shortcuts in the various binding dicts. 

2193 

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. 

2223 

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) 

2418 

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. 

2516 

2517 def func(event: Event=None) -> None: 

2518 c.gotoCommands.find_file_line(n=int(commandName)) 

2519 

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 

2565 

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) 

2635 

2636 def put(s: str) -> None: 

2637 g.es('', s, tabName=tabName) 

2638 

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() 

2746 

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() 

2755 

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 

2782 

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() 

2791 

2792 # New in Leo 5.4 

2793 

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. 

2811 

2812 Commands should use k.get1Arg to get the first minibuffer argument and 

2813 k.getNextArg to get all other arguments. 

2814 

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. 

2817 

2818 Example 1: get one argument from the user: 

2819 

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) 

2825 

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() 

2835 

2836 Example 2: get two arguments from the user: 

2837 

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) 

2843 

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) 

2849 

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() 

2859 

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. 

2864 

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. 

2869 

2870 The arguments to k.get1Arg are as follows: 

2871 

2872 event: The event passed to the command. 

2873 

2874 handler=None, An executable. k.get1arg calls handler(event) 

2875 when the user completes the argument by typing 

2876 <Return> or (sometimes) <tab>. 

2877 

2878 tabList=[]: A list of possible completions. 

2879 

2880 completion=True: True if completions are enabled. 

2881 

2882 oneCharacter=False: True if k.arg should be a single character. 

2883 

2884 stroke=None: The incoming key stroke. 

2885 

2886 useMinibuffer=True: True: put focus in the minibuffer while accumulating arguments. 

2887 False allows sort-lines, for example, to show the selection range. 

2888 

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) 

2895 

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) 

2912 

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) 

2916 

2917 def doTabCompletion(self, tabList: List[str]) -> None: 

2918 """Convenience method mapping k.doTabCompletion to ga.do_tab.""" 

2919 self.getArgInstance.do_tab(tabList) 

2920 

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. 

2960 

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. 

3018 

3019 You can wrap any method in a callback function, so the 

3020 restriction to functions is not significant. 

3021 

3022 Ignore the 'shortcut' arg unless 'allowBinding' is True. 

3023 

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. 

3061 

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: 

3176 

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. 

3204 

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*=') 

3461 

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 

3616 

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 

3621 

3622 # Start at c.p, then retry everywhere. 

3623 

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) 

3634 

3635 # Too confusing for the user. 

3636 # re_pat = re.compile(r'^@(\w)+[ \t](.+)') 

3637 

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: 

3654 

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) 

3753 

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. 

3767 

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 

3810 

3811 def enterModeCallback(event: Event=None, name: str=key) -> None: 

3812 k.enterNamedMode(event, name) 

3813 

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: 

3936 

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. 

3970 

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: 

4081 

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: 

4140 

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: 

4210 

4211 def __repr__(self) -> str: 

4212 return f"<ModeInfo {self.name}>" 

4213 

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: 

4218 

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(' ', '-') 

4286 

4287 def enterModeCallback(event: Event=None, self: Any=self) -> None: 

4288 self.enterMode() 

4289 

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: 

4295 

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: 

4321 

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