Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2#@+leo-ver=5-thin 

3#@+node:ekr.20061031131434: * @file leoKeys.py 

4#@@first 

5"""Gui-independent keystroke handling for Leo.""" 

6# pylint: disable=eval-used 

7# pylint: disable=deprecated-method 

8#@+<< imports >> 

9#@+node:ekr.20061031131434.1: ** << imports >> (leoKeys) 

10import inspect 

11import os 

12import re 

13import string 

14import sys 

15import textwrap 

16import time 

17from typing import Dict, List, Tuple 

18from leo.core import leoGlobals as g 

19from leo.commands import gotoCommands 

20from leo.external import codewise 

21try: 

22 import jedi 

23except ImportError: 

24 jedi = None 

25#@-<< imports >> 

26#@+<< Key bindings, an overview >> 

27#@+node:ekr.20130920121326.11281: ** << Key bindings, an overview >> 

28#@@language rest 

29#@+at 

30# The big pictures of key bindings: 

31# 

32# 1. Code in leoKeys.py and in leoConfig.py converts user key settings to 

33# various Python **binding dictionaries** defined in leoKeys.py. 

34# 

35# 2. An instance of LeoQtEventFilter should be attached to all visible panes 

36# in Leo's main window. g.app.gui.setFilter does this. 

37# 

38# 3. LeoQtEventFilter.eventFilter calls k.masterKeyhandler for every 

39# keystroke. eventFilter passes only just the event argument to 

40# k.masterKeyHandler. The event arg gives both the widget in which the 

41# event occurs and the keystroke. 

42# 

43# 4. k.masterKeyHandler and its helpers use the event argument and the 

44# binding dictionaries to execute the Leo command (if any) associated with 

45# the incoming keystroke. 

46# 

47# Important details: 

48# 

49# 1. g.app.gui.setFilter allows various traces and assertions to be made 

50# uniformly. The obj argument to setFilter is a QWidget object; the w 

51# argument to setFilter can be either the same as obj, or a Leo 

52# wrapper class. **Important**: the types of obj and w are not 

53# actually all that important, as discussed next. 

54# 

55# 2. The logic in k.masterKeyHandler and its helpers is long and involved: 

56# 

57# A. k.getPaneBinding associates a command with the incoming keystroke based 

58# on a) the widget's name and b) whether the widget is a text widget 

59# (which depends on the type of the widget). 

60# 

61# To do this, k.getPaneBinding uses a **binding priority table**. This 

62# table is defined within k.getPaneBinding itself. The table indicates 

63# which of several possible bindings should have priority. For instance, 

64# if the widget is a text widget, a user binding for a 'text' widget takes 

65# priority over a default key binding. Similarly, if the widget is Leo's 

66# tree widget, a 'tree' binding has top priority. There are many other 

67# details encapsulated in the table. The exactly details of the binding 

68# priority table are open to debate, but in practice the resulting 

69# bindings are as expeced. 

70# 

71# B. If k.getPaneBinding finds a command associated with the incoming 

72# keystroke, k.masterKeyHandler executes the command. 

73# 

74# C. If k.getPaneBinding fails to bind the incoming keystroke to a command, 

75# k.masterKeyHandler calls k.handleUnboundKeys to handle the keystroke. 

76# Depending on the widget, and settings, and the keystroke, 

77# k.handleUnboundKeys may do nothing, or it may call k.masterCommand to 

78# insert a plain key into the widget. 

79#@-<< Key bindings, an overview >> 

80#@+<< about 'internal' bindings >> 

81#@+node:ekr.20061031131434.2: ** << about 'internal' bindings >> 

82#@@language rest 

83#@+at 

84# Here are the rules for translating key bindings (in leoSettings.leo) 

85# into keys for k.bindingsDict: 

86# 

87# 1. The case of plain letters is significant: a is not A. 

88# 

89# 2. The Shift- prefix can be applied *only* to letters. Leo will ignore 

90# (with a warning) the shift prefix applied to any other binding, 

91# e.g., Ctrl-Shift-( 

92# 

93# 3. The case of letters prefixed by Ctrl-, Alt-, Key- or Shift- is 

94# *not* significant. Thus, the Shift- prefix is required if you want 

95# an upper-case letter (with the exception of 'bare' uppercase 

96# letters.) 

97# 

98# The following table illustrates these rules. In each row, the first 

99# entry is the key (for k.bindingsDict) and the other entries are 

100# equivalents that the user may specify in leoSettings.leo: 

101# 

102# a, Key-a, Key-A 

103# A, Shift-A 

104# Alt-a, Alt-A 

105# Alt-A, Alt-Shift-a, Alt-Shift-A 

106# Ctrl-a, Ctrl-A 

107# Ctrl-A, Ctrl-Shift-a, Ctrl-Shift-A 

108# , Key-!,Key-exclam,exclam 

109# 

110# This table is consistent with how Leo already works (because it is 

111# consistent with Tk's key-event specifiers). It is also, I think, the 

112# least confusing set of rules. 

113#@-<< about 'internal' bindings >> 

114#@+<< about key dicts >> 

115#@+node:ekr.20061031131434.3: ** << about key dicts >> 

116#@@language rest 

117#@+at 

118# ivar Keys Values 

119# ---- ---- ------ 

120# c.commandsDict command names (1) functions 

121# k.bindingsDict shortcuts lists of BindingInfo objects 

122# k.masterBindingsDict scope names (2) Interior masterBindingDicts (3) 

123# k.masterGuiBindingsDict strokes list of widgets in which stoke is bound 

124# inverseBindingDict (5) command names lists of tuples (pane,key) 

125# modeCommandsDict (6) command name (7) inner modeCommandsDicts (8) 

126# 

127# New in Leo 4.7: 

128# k.killedBindings is a list of command names for which bindings have been killed in local files. 

129# 

130# Notes: 

131# 

132# (1) Command names are minibuffer names (strings) 

133# (2) Scope names are 'all','text',etc. 

134# (3) Interior masterBindingDicts: Keys are strokes; values are BindingInfo objects. 

135# (5) inverseBindingDict is **not** an ivar: it is computed by k.computeInverseBindingDict. 

136# (6) A global dict: g.app.gui.modeCommandsDict 

137# (7) enter-x-command 

138# (8) Keys are command names, values are lists of BindingInfo objects. 

139#@-<< about key dicts >> 

140#@+others 

141#@+node:ekr.20150509035140.1: ** ac_cmd (decorator) 

142def ac_cmd(name): 

143 """Command decorator for the AutoCompleter class.""" 

144 return g.new_cmd_decorator(name, ['c', 'k', 'autoCompleter']) 

145#@+node:ekr.20150509035028.1: ** cmd (decorator) 

146def cmd(name): 

147 """Command decorator for the leoKeys class.""" 

148 return g.new_cmd_decorator(name, ['c', 'k',]) 

149#@+node:ekr.20061031131434.4: ** class AutoCompleterClass 

150class AutoCompleterClass: 

151 """A class that inserts autocompleted and calltip text in text widgets. 

152 This class shows alternatives in the tabbed log pane. 

153 

154 The keyHandler class contains hooks to support these characters: 

155 invoke-autocompleter-character (default binding is '.') 

156 invoke-calltips-character (default binding is '(') 

157 """ 

158 #@+others 

159 #@+node:ekr.20061031131434.5: *3* ac.ctor & reloadSettings 

160 def __init__(self, k): 

161 """Ctor for AutoCompleterClass class.""" 

162 # Ivars... 

163 self.c = k.c 

164 self.k = k 

165 self.language = None 

166 # additional namespaces to search for objects, other code 

167 # can append namespaces to this to extend scope of search 

168 self.namespaces = [] 

169 self.qw = None # The object that supports qcompletion methods. 

170 self.tabName = None # The name of the main completion tab. 

171 self.verbose = False # True: print all members, regardless of how many there are. 

172 self.w = None # The widget that gets focus after autocomplete is done. 

173 self.warnings = {} # Keys are language names. 

174 # Codewise pre-computes... 

175 self.codewiseSelfList = [] # The (global) completions for "self." 

176 self.completionsDict = {} # Keys are prefixes, values are completion lists. 

177 self.reloadSettings() 

178 

179 def reloadSettings(self): 

180 c = self.c 

181 self.auto_tab = c.config.getBool('auto-tab-complete', True) 

182 self.forbid_invalid = c.config.getBool('forbid-invalid-completions', False) 

183 self.use_jedi = c.config.getBool('use-jedi', False) 

184 # True: show results in autocompleter tab. 

185 # False: show results in a QCompleter widget. 

186 self.use_qcompleter = c.config.getBool('use-qcompleter', False) 

187 #@+node:ekr.20061031131434.8: *3* ac.Top level 

188 #@+node:ekr.20061031131434.9: *4* ac.autoComplete 

189 @ac_cmd('auto-complete') 

190 def autoComplete(self, event=None): 

191 """An event handler for autocompletion.""" 

192 c, k = self.c, self.k 

193 # pylint: disable=consider-using-ternary 

194 w = event and event.w or c.get_focus() 

195 if k.unboundKeyAction not in ('insert', 'overwrite'): 

196 return 

197 c.insertCharFromEvent(event) 

198 if c.exists: 

199 c.frame.updateStatusLine() 

200 # Allow autocompletion only in the body pane. 

201 if not c.widget_name(w).lower().startswith('body'): 

202 return 

203 self.language = g.scanForAtLanguage(c, c.p) 

204 if w and k.enable_autocompleter: 

205 self.w = w 

206 self.start(event) 

207 #@+node:ekr.20061031131434.10: *4* ac.autoCompleteForce 

208 @ac_cmd('auto-complete-force') 

209 def autoCompleteForce(self, event=None): 

210 """Show autocompletion, even if autocompletion is not presently enabled.""" 

211 c, k = self.c, self.k 

212 # pylint: disable=consider-using-ternary 

213 w = event and event.w or c.get_focus() 

214 if k.unboundKeyAction not in ('insert', 'overwrite'): 

215 return 

216 if c.exists: 

217 c.frame.updateStatusLine() 

218 # Allow autocompletion only in the body pane. 

219 if not c.widget_name(w).lower().startswith('body'): 

220 return 

221 self.language = g.scanForAtLanguage(c, c.p) 

222 if w: 

223 self.w = w 

224 self.start(event) 

225 

226 #@+node:ekr.20061031131434.12: *4* ac.enable/disable/toggleAutocompleter/Calltips 

227 @ac_cmd('disable-autocompleter') 

228 def disableAutocompleter(self, event=None): 

229 """Disable the autocompleter.""" 

230 self.k.enable_autocompleter = False 

231 self.showAutocompleterStatus() 

232 

233 @ac_cmd('disable-calltips') 

234 def disableCalltips(self, event=None): 

235 """Disable calltips.""" 

236 self.k.enable_calltips = False 

237 self.showCalltipsStatus() 

238 

239 @ac_cmd('enable-autocompleter') 

240 def enableAutocompleter(self, event=None): 

241 """Enable the autocompleter.""" 

242 self.k.enable_autocompleter = True 

243 self.showAutocompleterStatus() 

244 

245 @ac_cmd('enable-calltips') 

246 def enableCalltips(self, event=None): 

247 """Enable calltips.""" 

248 self.k.enable_calltips = True 

249 self.showCalltipsStatus() 

250 

251 @ac_cmd('toggle-autocompleter') 

252 def toggleAutocompleter(self, event=None): 

253 """Toggle whether the autocompleter is enabled.""" 

254 self.k.enable_autocompleter = not self.k.enable_autocompleter 

255 self.showAutocompleterStatus() 

256 

257 @ac_cmd('toggle-calltips') 

258 def toggleCalltips(self, event=None): 

259 """Toggle whether calltips are enabled.""" 

260 self.k.enable_calltips = not self.k.enable_calltips 

261 self.showCalltipsStatus() 

262 #@+node:ekr.20061031131434.13: *4* ac.showCalltips 

263 @ac_cmd('show-calltips') 

264 def showCalltips(self, event=None): 

265 """Show the calltips at the cursor.""" 

266 c, k, w = self.c, self.c.k, event and event.w 

267 if not w: 

268 return 

269 is_headline = c.widget_name(w).startswith('head') 

270 if k.enable_calltips and not is_headline: 

271 self.w = w 

272 self.calltip() 

273 else: 

274 c.insertCharFromEvent(event) 

275 #@+node:ekr.20061031131434.14: *4* ac.showCalltipsForce 

276 @ac_cmd('show-calltips-force') 

277 def showCalltipsForce(self, event=None): 

278 """Show the calltips at the cursor, even if calltips are not presently enabled.""" 

279 c, w = self.c, event and event.w 

280 if not w: 

281 return 

282 is_headline = c.widget_name(w).startswith('head') 

283 if not is_headline: 

284 self.w = w 

285 self.calltip() 

286 else: 

287 c.insertCharFromEvent(event) 

288 #@+node:ekr.20061031131434.15: *4* ac.showAutocompleter/CalltipsStatus 

289 def showAutocompleterStatus(self): 

290 """Show the autocompleter status.""" 

291 k = self.k 

292 if not g.unitTesting: 

293 s = f"autocompleter {'On' if k.enable_autocompleter else 'Off'}" 

294 g.red(s) 

295 

296 def showCalltipsStatus(self): 

297 """Show the autocompleter status.""" 

298 k = self.k 

299 if not g.unitTesting: 

300 s = f"calltips {'On'}" if k.enable_calltips else 'Off' 

301 g.red(s) 

302 #@+node:ekr.20061031131434.16: *3* ac.Helpers 

303 #@+node:ekr.20110512212836.14469: *4* ac.exit 

304 def exit(self): 

305 

306 trace = all(z in g.app.debug for z in ('abbrev', 'verbose')) 

307 if trace: 

308 g.trace('(AutoCompleterClass)') 

309 c, p, u = self.c, self.c.p, self.c.undoer 

310 w = self.w or c.frame.body.wrapper 

311 c.k.keyboardQuit() 

312 if self.use_qcompleter: 

313 if self.qw: 

314 self.qw.end_completer() 

315 self.qw = None # Bug fix: 2013/09/24. 

316 else: 

317 for name in (self.tabName, 'Modules', 'Info'): 

318 c.frame.log.deleteTab(name) 

319 # Restore the selection range that may have been destroyed by changing tabs. 

320 c.widgetWantsFocusNow(w) 

321 i, j = w.getSelectionRange() 

322 w.setSelectionRange(i, j, insert=j) 

323 newText = w.getAllText() 

324 if p.b == newText: 

325 return 

326 bunch = u.beforeChangeBody(p) 

327 p.v.b = newText # p.b would cause a redraw. 

328 u.afterChangeBody(p, 'auto-completer', bunch) 

329 

330 finish = exit 

331 abort = exit 

332 #@+node:ekr.20061031131434.18: *4* ac.append/begin/popTabName 

333 def appendTabName(self, word): 

334 self.setTabName(self.tabName + '.' + word) 

335 

336 def beginTabName(self, word): 

337 self.setTabName('AutoComplete ' + word) 

338 

339 def clearTabName(self): 

340 self.setTabName('AutoComplete ') 

341 

342 def popTabName(self): 

343 s = self.tabName 

344 i = s.rfind('.', 0, -1) 

345 if i > -1: 

346 self.setTabName(s[0:i]) 

347 

348 # Underscores are not valid in Pmw tab names! 

349 

350 def setTabName(self, s): 

351 c = self.c 

352 if self.tabName: 

353 c.frame.log.deleteTab(self.tabName) 

354 self.tabName = s.replace('_', '') or '' 

355 c.frame.log.clearTab(self.tabName) 

356 #@+node:ekr.20110509064011.14556: *4* ac.attr_matches 

357 def attr_matches(self, s, namespace): 

358 """Compute matches when string s is of the form name.name....name. 

359 

360 Evaluates s using eval(s,namespace) 

361 

362 Assuming the text is of the form NAME.NAME....[NAME], and is evaluatable in 

363 the namespace, it will be evaluated and its attributes (as revealed by 

364 dir()) are used as possible completions. 

365 

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

367 

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

369 with a __getattr__ hook is evaluated. 

370 

371 """ 

372 # Seems to work great. Catches things like ''.<tab> 

373 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", s) 

374 if not m: 

375 return [] 

376 expr, attr = m.group(1, 3) 

377 try: 

378 safe_expr = self.strip_brackets(expr) 

379 obj = eval(safe_expr, namespace) 

380 except Exception: 

381 return [] 

382 # Build the result. 

383 words = dir(obj) 

384 n = len(attr) 

385 result = [f"{expr}.{w}" for w in words if w[:n] == attr] 

386 return result 

387 #@+node:ekr.20061031131434.11: *4* ac.auto_completer_state_handler 

388 def auto_completer_state_handler(self, event): 

389 """Handle all keys while autocompleting.""" 

390 c, k, tag = self.c, self.k, 'auto-complete' 

391 state = k.getState(tag) 

392 ch = event.char if event else '' 

393 stroke = event.stroke if event else '' 

394 is_plain = k.isPlainKey(stroke) 

395 if state == 0: 

396 c.frame.log.clearTab(self.tabName) 

397 common_prefix, prefix, tabList = self.compute_completion_list() 

398 if tabList: 

399 k.setState(tag, 1, handler=self.auto_completer_state_handler) 

400 else: 

401 self.exit() 

402 elif ch in ('\n', 'Return'): 

403 self.exit() 

404 elif ch == 'Escape': 

405 self.exit() 

406 elif ch in ('\t', 'Tab'): 

407 self.compute_completion_list() 

408 elif ch in ('\b', 'BackSpace'): 

409 self.do_backspace() 

410 elif ch == '.': 

411 self.insert_string('.') 

412 self.compute_completion_list() 

413 elif ch == '?': 

414 self.info() 

415 elif ch == '!': 

416 # Toggle between verbose and brief listing. 

417 self.verbose = not self.verbose 

418 kind = 'ON' if self.verbose else 'OFF' 

419 message = f"verbose completions {kind}" 

420 g.es_print(message) 

421 # This doesn't work because compute_completion_list clears the autocomplete tab. 

422 # self.put('', message, tabName=self.tabName) 

423 # This is almost invisible: the fg='red' is not honored. 

424 c.frame.putStatusLine(message, fg='red') 

425 self.compute_completion_list() 

426 # elif ch == 'Down' and hasattr(self,'onDown'): 

427 # self.onDown() 

428 # elif ch == 'Up' and hasattr(self,'onUp'): 

429 # self.onUp() 

430 elif is_plain and ch and ch in string.printable: 

431 self.insert_general_char(ch) 

432 elif stroke == k.autoCompleteForceKey: 

433 # This is probably redundant because completions will exist. 

434 # However, it doesn't hurt, and it may be useful rarely. 

435 common_prefix, prefix, tabList = self.compute_completion_list() 

436 if tabList: 

437 self.show_completion_list(common_prefix, prefix, tabList) 

438 else: 

439 g.warning('No completions') 

440 self.exit() 

441 else: 

442 self.abort() 

443 return 'do-standard-keys' 

444 return None 

445 #@+node:ekr.20061031131434.20: *4* ac.calltip & helpers 

446 def calltip(self): 

447 """Show the calltips for the present prefix. 

448 ch is '(' if the user has just typed it. 

449 """ 

450 obj, prefix = self.get_object() 

451 if obj: 

452 self.calltip_success(prefix, obj) 

453 else: 

454 self.calltip_fail(prefix) 

455 self.exit() 

456 #@+node:ekr.20110512090917.14468: *5* ac.calltip_fail 

457 def calltip_fail(self, prefix): 

458 """Evaluation of prefix failed.""" 

459 self.insert_string('(') 

460 #@+node:ekr.20110512090917.14469: *5* ac.calltip_success 

461 def calltip_success(self, prefix, obj): 

462 try: 

463 # Get the parenthesized argument list. 

464 s1, s2, s3, s4 = inspect.getargspec(obj) 

465 s = inspect.formatargspec(s1, s2, s3, s4) 

466 except Exception: 

467 self.insert_string('(') 

468 return 

469 # Clean s and insert it: don't include the opening "(". 

470 if g.match(s, 1, 'self,'): 

471 s = s[6:].strip() 

472 elif g.match_word(s, 1, 'self'): 

473 s = s[5:].strip() 

474 else: 

475 s = s[1:].strip() 

476 self.insert_string("(", select=False) 

477 self.insert_string(s, select=True) 

478 #@+node:ekr.20061031131434.28: *4* ac.compute_completion_list & helper 

479 def compute_completion_list(self): 

480 """Return the autocompleter completion list.""" 

481 prefix = self.get_autocompleter_prefix() 

482 key, options = self.get_cached_options(prefix) 

483 if not options: 

484 options = self.get_completions(prefix) 

485 tabList, common_prefix = g.itemsMatchingPrefixInList( 

486 prefix, options, matchEmptyPrefix=False) 

487 if not common_prefix: 

488 tabList, common_prefix = g.itemsMatchingPrefixInList( 

489 prefix, options, matchEmptyPrefix=True) 

490 if tabList: 

491 self.show_completion_list(common_prefix, prefix, tabList) 

492 return common_prefix, prefix, tabList 

493 #@+node:ekr.20110514051607.14524: *5* ac.get_cached_options 

494 def get_cached_options(self, prefix): 

495 d = self.completionsDict 

496 # Search the completions Dict for shorter and shorter prefixes. 

497 i = len(prefix) 

498 while i > 0: 

499 key = prefix[:i] 

500 i -= 1 

501 # Make sure we report hits only of real objects. 

502 if key.endswith('.'): 

503 return key, [] 

504 options = d.get(key) 

505 if options: 

506 return key, options 

507 return None, [] 

508 #@+node:ekr.20061031131434.29: *4* ac.do_backspace 

509 def do_backspace(self): 

510 """Delete the character and recompute the completion list.""" 

511 c, w = self.c, self.w 

512 c.bodyWantsFocusNow() 

513 i = w.getInsertPoint() 

514 if i <= 0: 

515 self.exit() 

516 return 

517 w.delete(i - 1, i) 

518 w.setInsertPoint(i - 1) 

519 if i <= 1: 

520 self.exit() 

521 else: 

522 # Update the list. Abort if there is no prefix. 

523 common_prefix, prefix, tabList = self.compute_completion_list() 

524 if not prefix: 

525 self.exit() 

526 #@+node:ekr.20110510133719.14548: *4* ac.do_qcompleter_tab (not used) 

527 def do_qcompleter_tab(self, prefix, options): 

528 """Return the longest common prefix of all the options.""" 

529 matches, common_prefix = g.itemsMatchingPrefixInList( 

530 prefix, options, matchEmptyPrefix=False) 

531 return common_prefix 

532 #@+node:ekr.20110509064011.14561: *4* ac.get_autocompleter_prefix 

533 def get_autocompleter_prefix(self): 

534 # Only the body pane supports auto-completion. 

535 w = self.c.frame.body.wrapper 

536 s = w.getAllText() 

537 if not s: 

538 return '' 

539 i = w.getInsertPoint() - 1 

540 i = j = max(0, i) 

541 while i >= 0 and (s[i].isalnum() or s[i] in '._'): 

542 i -= 1 

543 i += 1 

544 j += 1 

545 prefix = s[i:j] 

546 return prefix 

547 #@+node:ekr.20110512212836.14471: *4* ac.get_completions & helpers 

548 jedi_warning = False 

549 

550 def get_completions(self, prefix): 

551 """Return jedi or codewise completions.""" 

552 d = self.completionsDict 

553 if self.use_jedi: 

554 try: 

555 import jedi 

556 except ImportError: 

557 jedi = None 

558 if not self.jedi_warning: 

559 self.jedi_warning = True 

560 g.es_print('can not import jedi') 

561 g.es_print('ignoring @bool use_jedi = True') 

562 if jedi: 

563 aList = ( 

564 self.get_jedi_completions(prefix) or 

565 # Prefer the jedi completions. 

566 self.get_leo_completions(prefix)) 

567 d[prefix] = aList 

568 return aList 

569 # 

570 # Not jedi. Use codewise. 

571 # Precompute the codewise completions for '.self'. 

572 if not self.codewiseSelfList: 

573 aList = self.get_codewise_completions('self.') 

574 self.codewiseSelfList = [z[5:] for z in aList] 

575 d['self.'] = self.codewiseSelfList 

576 # Use the cached list if it exists. 

577 aList = d.get(prefix) 

578 if aList: 

579 return aList 

580 aList = ( 

581 self.get_leo_completions(prefix) or 

582 # Prefer the Leo completions. 

583 self.get_codewise_completions(prefix) 

584 ) 

585 d[prefix] = aList 

586 return aList 

587 #@+node:ekr.20110510120621.14539: *5* ac.get_codewise_completions & helpers 

588 def get_codewise_completions(self, prefix): 

589 """Use codewise to generate a list of hits.""" 

590 c = self.c 

591 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", prefix) 

592 if m: 

593 varname = m.group(1) 

594 ivar = m.group(3) 

595 kind, aList = self.guess_class(c, varname) 

596 else: 

597 kind, aList = 'none', [] 

598 varname, ivar = None, None 

599 if aList: 

600 if kind == 'class': 

601 hits = self.lookup_methods(aList, ivar) 

602 hits.extend(self.codewiseSelfList) 

603 elif kind == 'module': 

604 hits = self.lookup_modules(aList, ivar) 

605 else: 

606 aList2 = prefix.split('.') 

607 if aList2: 

608 func = aList2[-1] 

609 hits = self.lookup_functions(func) 

610 else: 

611 hits = [] 

612 if 1: # A kludge: add the prefix to each hit. 

613 hits = [f"{varname}.{z}" for z in hits] 

614 return hits 

615 #@+node:ekr.20110510120621.14540: *6* ac.clean 

616 def clean(self, hits): 

617 """Clean up hits, a list of ctags patterns, for use in completion lists.""" 

618 # Just take the function name: ignore the signature & file. 

619 aList = list(set([z[0] for z in hits])) 

620 aList.sort() 

621 return aList 

622 #@+node:ekr.20110512232915.14481: *6* ac.clean_for_display (not used) 

623 def clean_for_display(self, hits): 

624 """Clean up hits, a list of ctags patterns, for display purposes.""" 

625 aList = [] 

626 for h in hits: 

627 s = h[0] 

628 # Display oriented: no good for completion list. 

629 fn = h[1].strip() 

630 if fn.startswith('/'): 

631 sig = fn[2:-4].strip() 

632 else: 

633 sig = fn 

634 aList.append(f"{s}: {sig}") 

635 aList = list(set(aList)) 

636 aList.sort() 

637 return aList 

638 #@+node:ekr.20110510120621.14542: *6* ac.guess_class 

639 def guess_class(self, c, varname) -> Tuple[str, List[str]]: 

640 """Return kind, class_list""" 

641 # if varname == 'g': 

642 # return 'module',['leoGlobals'] 

643 if varname == 'p': 

644 return 'class', ['position'] 

645 if varname == 'c': 

646 return 'class', ['Commands'] 

647 if varname == 'self': 

648 # Return the nearest enclosing class. 

649 for p in c.p.parents(): 

650 h = p.h 

651 m = re.search(r'class\s+(\w+)', h) 

652 if m: 

653 return 'class', [m.group(1)] 

654 # This is not needed now that we add the completions for 'self'. 

655 # aList = ContextSniffer().get_classes(c.p.b, varname) 

656 return 'class', [] 

657 #@+node:ekr.20110510120621.14543: *6* ac.lookup_functions/methods/modules 

658 def lookup_functions(self, prefix): 

659 aList = codewise.cmd_functions([prefix]) 

660 hits = [z.split(None, 1) for z in aList if z.strip()] 

661 return self.clean(hits) 

662 

663 def lookup_methods(self, aList, prefix): # prefix not used, only aList[0] used. 

664 aList = codewise.cmd_members([aList[0]]) 

665 hits = [z.split(None, 1) for z in aList if z.strip()] 

666 return self.clean(hits) 

667 

668 def lookup_modules(self, aList, prefix): # prefix not used, only aList[0] used. 

669 aList = codewise.cmd_functions([aList[0]]) 

670 hits = [z.split(None, 1) for z in aList if z.strip()] 

671 return self.clean(hits) 

672 #@+node:ekr.20180519111302.1: *5* ac.get_jedi_completions & helper 

673 def get_jedi_completions(self, prefix): 

674 

675 c = self.c 

676 w = c.frame.body.wrapper 

677 i = w.getInsertPoint() 

678 p = c.p 

679 body_s = p.b 

680 # 

681 # Get the entire source for jedi. 

682 t1 = time.process_time() 

683 goto = gotoCommands.GoToCommands(c) 

684 root, fileName = goto.find_root(p) 

685 if root: 

686 source = goto.get_external_file_with_sentinels(root=root or p) 

687 n0 = goto.find_node_start(p=p, s=source) 

688 if n0 is None: 

689 n0 = 0 

690 else: 

691 source = body_s 

692 n0 = 0 

693 t2 = time.process_time() 

694 # 

695 # Get local line 

696 lines = g.splitLines(body_s) 

697 row, column = g.convertPythonIndexToRowCol(body_s, i) 

698 if row >= len(lines): # 2020/11/27 

699 return [] 

700 line = lines[row] 

701 # 

702 # Find the global line, and compute offsets. 

703 source_lines = g.splitLines(source) 

704 for jedi_line, g_line in enumerate(source_lines[n0:]): 

705 if line.lstrip() == g_line.lstrip(): 

706 # Adjust the column. 

707 indent1 = len(line) - len(line.lstrip()) 

708 indent2 = len(g_line) - len(g_line.lstrip()) 

709 if indent2 >= indent1: 

710 local_column = column # For traces. 

711 column += abs(indent2 - indent1) 

712 break 

713 else: 

714 completions = None 

715 jedi_line, indent1, indent2 = None, None, None 

716 if 0: # This *can* happen. 

717 g.printObj(source_lines[n0 - 1 : n0 + 30]) 

718 print(f"can not happen: not found: {line!r}") 

719 # 

720 # Get the jedi completions. 

721 if jedi and jedi_line is not None: 

722 try: 

723 # https://jedi.readthedocs.io/en/latest/docs/api.html#script 

724 script = jedi.Script(source, path=g.shortFileName(fileName)) 

725 completions = script.complete( 

726 line=1 + n0 + jedi_line, 

727 column=column, 

728 ) 

729 t3 = time.process_time() 

730 except Exception: 

731 t3 = time.process_time() 

732 completions = None 

733 g.printObj(source_lines[n0 - 1 : n0 + 30]) 

734 print('ERROR', p.h) 

735 if not completions: 

736 return [] 

737 # May be used in traces below. 

738 assert t3 >= t2 >= t1 

739 assert local_column is not None 

740 completions = [z.name for z in completions] 

741 completions = [self.add_prefix(prefix, z) for z in completions] 

742 # Retain these for now... 

743 # g.printObj(completions[:5]) 

744 # head = line[:local_column] 

745 # ch = line[local_column:local_column+1] 

746 # g.trace(len(completions), repr(ch), head.strip()) 

747 return completions 

748 #@+node:ekr.20180526211127.1: *6* ac.add_prefix 

749 def add_prefix(self, prefix, s): 

750 """A hack to match the callers expectations.""" 

751 if prefix.find('.') > -1: 

752 aList = prefix.split('.') 

753 prefix = '.'.join(aList[:-1]) + '.' 

754 return s if s.startswith(prefix) else prefix + s 

755 #@+node:ekr.20110509064011.14557: *5* ac.get_leo_completions 

756 def get_leo_completions(self, prefix): 

757 """Return completions in an environment defining c, g and p.""" 

758 aList = [] 

759 for d in self.namespaces + [self.get_leo_namespace(prefix)]: 

760 aList.extend(self.attr_matches(prefix, d)) 

761 aList.sort() 

762 return aList 

763 #@+node:ekr.20110512090917.14466: *4* ac.get_leo_namespace 

764 def get_leo_namespace(self, prefix): 

765 """ 

766 Return an environment in which to evaluate prefix. 

767 Add some common standard library modules as needed. 

768 """ 

769 k = self.k 

770 d = {'c': k.c, 'p': k.c.p, 'g': g} 

771 aList = prefix.split('.') 

772 if len(aList) > 1: 

773 name = aList[0] 

774 m = sys.modules.get(name) 

775 if m: 

776 d[name] = m 

777 return d 

778 #@+node:ekr.20110512170111.14472: *4* ac.get_object 

779 def get_object(self): 

780 """Return the object corresponding to the current prefix.""" 

781 common_prefix, prefix1, aList = self.compute_completion_list() 

782 if not aList: 

783 return None, prefix1 

784 if len(aList) == 1: 

785 prefix = aList[0] 

786 else: 

787 prefix = common_prefix 

788 if prefix.endswith('.') and self.use_qcompleter: 

789 prefix += self.qcompleter.get_selection() 

790 safe_prefix = self.strip_brackets(prefix) 

791 for d in self.namespaces + [self.get_leo_namespace(prefix)]: 

792 try: 

793 obj = eval(safe_prefix, d) 

794 break # only reached if none of the exceptions below occur 

795 except AttributeError: 

796 obj = None 

797 except NameError: 

798 obj = None 

799 except SyntaxError: 

800 obj = None 

801 except Exception: 

802 g.es_exception() 

803 obj = None 

804 return obj, prefix 

805 #@+node:ekr.20061031131434.38: *4* ac.info 

806 def info(self): 

807 """Show the docstring for the present completion.""" 

808 c = self.c 

809 obj, prefix = self.get_object() 

810 c.frame.log.clearTab('Info', wrap='word') 

811 put = lambda s: self.put('', s, tabName='Info') 

812 put(prefix) 

813 try: 

814 argspec = inspect.getargspec(obj) 

815 # uses None instead of empty list 

816 argn = len(argspec.args or []) 

817 defn = len(argspec.defaults or []) 

818 put("args:") 

819 simple_args = argspec.args[: argn - defn] 

820 if not simple_args: 

821 put(' (none)') 

822 else: 

823 put(' ' + ', '.join(' ' + i for i in simple_args)) 

824 put("keyword args:") 

825 if not argspec.defaults: 

826 put(' (none)') 

827 for i in range(defn): 

828 arg = argspec.args[-defn + i] 

829 put(f" {arg} = {repr(argspec.defaults[i])}") 

830 if argspec.varargs: 

831 put("varargs: *" + argspec.varargs) 

832 if argspec.keywords: 

833 put("keywords: **" + argspec.keywords) 

834 put('\n') # separate docstring 

835 except TypeError: 

836 put('\n') # not a callable 

837 doc = inspect.getdoc(obj) 

838 put(doc if doc else "No docstring for " + repr(prefix)) 

839 #@+node:ekr.20110510071925.14586: *4* ac.init_qcompleter 

840 def init_qcompleter(self, event=None): 

841 

842 # Compute the prefix and the list of options. 

843 prefix = self.get_autocompleter_prefix() 

844 options = self.get_completions(prefix) 

845 w = self.c.frame.body.wrapper.widget # A LeoQTextBrowser. May be none for unit tests. 

846 if w and options: 

847 self.qw = w 

848 self.qcompleter = w.init_completer(options) 

849 self.auto_completer_state_handler(event) 

850 else: 

851 if not g.unitTesting: 

852 g.warning('No completions') 

853 self.exit() 

854 #@+node:ekr.20110511133940.14552: *4* ac.init_tabcompleter 

855 def init_tabcompleter(self, event=None): 

856 # Compute the prefix and the list of options. 

857 prefix = self.get_autocompleter_prefix() 

858 options = self.get_completions(prefix) 

859 if options: 

860 self.clearTabName() # Creates the tabbed pane. 

861 self.auto_completer_state_handler(event) 

862 else: 

863 g.warning('No completions') 

864 self.exit() 

865 #@+node:ekr.20061031131434.39: *4* ac.insert_general_char 

866 def insert_general_char(self, ch): 

867 

868 trace = all(z in g.app.debug for z in ('abbrev', 'verbose')) 

869 k, w = self.k, self.w 

870 if g.isWordChar(ch): 

871 if trace: 

872 g.trace('ch', repr(ch)) 

873 self.insert_string(ch) 

874 common_prefix, prefix, aList = self.compute_completion_list() 

875 if not aList: 

876 if self.forbid_invalid: 

877 # Delete the character we just inserted. 

878 self.do_backspace() 

879 # @bool auto_tab_complete is deprecated. 

880 # Auto-completion makes no sense if it is False. 

881 elif self.auto_tab and len(common_prefix) > len(prefix): 

882 extend = common_prefix[len(prefix) :] 

883 ins = w.getInsertPoint() 

884 if trace: 

885 g.trace('extend', repr(extend)) 

886 w.insert(ins, extend) 

887 return 

888 if ch == '(' and k.enable_calltips: 

889 # This calls self.exit if the '(' is valid. 

890 self.calltip() 

891 else: 

892 if trace: 

893 g.trace('ch', repr(ch)) 

894 self.insert_string(ch) 

895 self.exit() 

896 #@+node:ekr.20061031131434.31: *4* ac.insert_string 

897 def insert_string(self, s, select=False): 

898 """ 

899 Insert an auto-completion string s at the insertion point. 

900 

901 Leo 6.4. This *part* of auto-completion is no longer undoable. 

902 """ 

903 c, w = self.c, self.w 

904 if not g.isTextWrapper(w): 

905 return 

906 c.widgetWantsFocusNow(w) 

907 # 

908 # Don't make this undoable. 

909 # oldText = w.getAllText() 

910 # oldSel = w.getSelectionRange() 

911 # bunch = u.beforeChangeBody(p) 

912 i = w.getInsertPoint() 

913 w.insert(i, s) 

914 if select: 

915 j = i + len(s) 

916 w.setSelectionRange(i, j, insert=j) 

917 # 

918 # Don't make this undoable. 

919 # if 0: 

920 # u.doTyping(p, 'Typing', 

921 # oldSel=oldSel, 

922 # oldText=oldText, 

923 # newText=w.getAllText(), 

924 # newInsert=w.getInsertPoint(), 

925 # newSel=w.getSelectionRange()) 

926 # else: 

927 # u.afterChangeBody(p, 'auto-complete', bunch) 

928 if self.use_qcompleter and self.qw: 

929 c.widgetWantsFocusNow(self.qw.leo_qc) 

930 #@+node:ekr.20110314115639.14269: *4* ac.is_leo_source_file 

931 def is_leo_source_file(self): 

932 """Return True if this is one of Leo's source files.""" 

933 c = self.c 

934 table = (z.lower() for z in ( 

935 'leoDocs.leo', 

936 'LeoGui.leo', 'LeoGuiPluginsRef.leo', 

937 # 'leoPlugins.leo', 'leoPluginsRef.leo', 

938 'leoPy.leo', 'leoPyRef.leo', 

939 'myLeoSettings.leo', 'leoSettings.leo', 

940 'ekr.leo', 

941 # 'test.leo', 

942 )) 

943 return c.shortFileName().lower() in table 

944 #@+node:ekr.20101101175644.5891: *4* ac.put 

945 def put(self, *args, **keys): 

946 """Put s to the given tab. 

947 

948 May be overridden in subclasses.""" 

949 # print('autoCompleter.put',args,keys) 

950 if g.unitTesting: 

951 pass 

952 else: 

953 g.es(*args, **keys) 

954 #@+node:ekr.20110511133940.14561: *4* ac.show_completion_list & helpers 

955 def show_completion_list(self, common_prefix, prefix, tabList): 

956 

957 c = self.c 

958 aList = common_prefix.split('.') 

959 header = '.'.join(aList[:-1]) 

960 # "!" toggles self.verbose. 

961 if self.verbose or self.use_qcompleter or len(tabList) < 20: 

962 tabList = self.clean_completion_list(header, tabList,) 

963 else: 

964 tabList = self.get_summary_list(header, tabList) 

965 if self.use_qcompleter: 

966 # Put the completions in the QListView. 

967 if self.qw: 

968 self.qw.show_completions(tabList) 

969 else: 

970 # Update the tab name, creating the tab if necessary. 

971 c.widgetWantsFocus(self.w) 

972 c.frame.log.clearTab(self.tabName) 

973 self.beginTabName(header + '.' if header else '') 

974 s = '\n'.join(tabList) 

975 self.put('', s, tabName=self.tabName) 

976 #@+node:ekr.20110513104728.14453: *5* ac.clean_completion_list 

977 def clean_completion_list(self, header, tabList): 

978 """Return aList with header removed from the start of each list item.""" 

979 return [ 

980 z[len(header) + 1 :] if z.startswith(header) else z 

981 for z in tabList] 

982 #@+node:ekr.20110513104728.14454: *5* ac.get_summary_list 

983 def get_summary_list(self, header, tabList): 

984 """Show the possible starting letters, 

985 but only if there are more than one. 

986 """ 

987 d: Dict[str, int] = {} 

988 for z in tabList: 

989 tail = z[len(header) :] if z else '' 

990 if tail.startswith('.'): 

991 tail = tail[1:] 

992 ch = tail[0] if tail else '' 

993 if ch: 

994 n = d.get(ch, 0) 

995 d[ch] = n + 1 

996 aList = [f"{ch2} {d.get(ch2)}" for ch2 in sorted(d)] 

997 if len(aList) > 1: 

998 tabList = aList 

999 else: 

1000 tabList = self.clean_completion_list(header, tabList) 

1001 return tabList 

1002 #@+node:ekr.20061031131434.46: *4* ac.start 

1003 def start(self, event): 

1004 """Init the completer and start the state handler.""" 

1005 # We don't need to clear this now that we don't use ContextSniffer. 

1006 c = self.c 

1007 if c.config.getBool('use-jedi', default=True): 

1008 self.completionsDict = {} 

1009 if self.use_qcompleter: 

1010 self.init_qcompleter(event) 

1011 else: 

1012 self.init_tabcompleter(event) 

1013 #@+node:ekr.20110512170111.14471: *4* ac.strip_brackets 

1014 def strip_brackets(self, s): 

1015 """Return s with all brackets removed. 

1016 

1017 This (mostly) ensures that eval will not execute function calls, etc. 

1018 """ 

1019 for ch in '[]{}()': 

1020 s = s.replace(ch, '') 

1021 return s 

1022 #@-others 

1023#@+node:ekr.20110312162243.14260: ** class ContextSniffer 

1024class ContextSniffer: 

1025 """ Class to analyze surrounding context and guess class 

1026 

1027 For simple dynamic code completion engines. 

1028 """ 

1029 

1030 def __init__(self): 

1031 self.vars = {} 

1032 # Keys are var names; values are list of classes 

1033 #@+others 

1034 #@+node:ekr.20110312162243.14261: *3* get_classes 

1035 def get_classes(self, s, varname): 

1036 """Return a list of classes for string s.""" 

1037 self.push_declarations(s) 

1038 aList = self.vars.get(varname, []) 

1039 return aList 

1040 #@+node:ekr.20110312162243.14262: *3* set_small_context 

1041 # def set_small_context(self, body): 

1042 # """ Set immediate function """ 

1043 # self.push_declarations(body) 

1044 #@+node:ekr.20110312162243.14263: *3* push_declarations & helper 

1045 def push_declarations(self, s): 

1046 for line in s.splitlines(): 

1047 line = line.lstrip() 

1048 if line.startswith('#'): 

1049 line = line.lstrip('#') 

1050 parts = line.split(':') 

1051 if len(parts) == 2: 

1052 a, b = parts 

1053 self.declare(a.strip(), b.strip()) 

1054 #@+node:ekr.20110312162243.14264: *4* declare 

1055 def declare(self, var, klass): 

1056 vars = self.vars.get(var, []) 

1057 if not vars: 

1058 self.vars[var] = vars 

1059 vars.append(klass) 

1060 #@-others 

1061#@+node:ekr.20140813052702.18194: ** class FileNameChooser 

1062class FileNameChooser: 

1063 """A class encapsulation file selection & completion logic.""" 

1064 #@+others 

1065 #@+node:ekr.20140813052702.18195: *3* fnc.__init__ 

1066 def __init__(self, c): 

1067 """Ctor for FileNameChooser class.""" 

1068 self.c = c 

1069 self.k = c.k 

1070 assert c and c.k 

1071 self.log = c.frame.log or g.NullObject() 

1072 self.callback = None 

1073 self.filterExt = None 

1074 self.log = None # inited later. 

1075 self.prompt = None 

1076 self.tabName = None 

1077 #@+node:ekr.20140813052702.18196: *3* fnc.compute_tab_list 

1078 def compute_tab_list(self): 

1079 """Compute the list of completions.""" 

1080 path = self.get_label() 

1081 # #215: insert-file-name doesn't process ~ 

1082 path = g.os_path_expanduser(path) 

1083 sep = os.path.sep 

1084 if g.os_path_exists(path): 

1085 if g.os_path_isdir(path): 

1086 if path.endswith(os.sep): 

1087 aList = g.glob_glob(path + '*') 

1088 else: 

1089 aList = g.glob_glob(path + sep + '*') 

1090 tabList = [z + sep if g.os_path_isdir(z) else z for z in aList] 

1091 else: 

1092 # An existing file. 

1093 tabList = [path] 

1094 else: 

1095 if path and path.endswith(sep): 

1096 path = path[:-1] 

1097 aList = g.glob_glob(path + '*') 

1098 tabList = [z + sep if g.os_path_isdir(z) else z for z in aList] 

1099 if self.filterExt: 

1100 for ext in self.filterExt: 

1101 tabList = [z for z in tabList if not z.endswith(ext)] 

1102 tabList = [g.os_path_normslashes(z) for z in tabList] 

1103 junk, common_prefix = g.itemsMatchingPrefixInList(path, tabList) 

1104 return common_prefix, tabList 

1105 #@+node:ekr.20140813052702.18197: *3* fnc.do_back_space 

1106 def do_back_space(self): 

1107 """Handle a back space.""" 

1108 w = self.c.k.w 

1109 if w and w.hasSelection(): 

1110 # s = w.getAllText() 

1111 i, j = w.getSelectionRange() 

1112 w.delete(i, j) 

1113 s = self.get_label() 

1114 else: 

1115 s = self.get_label() 

1116 if s: 

1117 s = s[:-1] 

1118 self.set_label(s) 

1119 if s: 

1120 common_prefix, tabList = self.compute_tab_list() 

1121 # Do *not* extend the label to the common prefix. 

1122 else: 

1123 tabList = [] 

1124 self.show_tab_list(tabList) 

1125 #@+node:ekr.20140813052702.18198: *3* fnc.do_char 

1126 def do_char(self, char): 

1127 """Handle a non-special character.""" 

1128 w = self.c.k.w 

1129 if w and w.hasSelection: 

1130 # s = w.getAllText() 

1131 i, j = w.getSelectionRange() 

1132 w.delete(i, j) 

1133 w.setInsertPoint(i) 

1134 w.insert(i, char) 

1135 else: 

1136 self.extend_label(char) 

1137 common_prefix, tabList = self.compute_tab_list() 

1138 self.show_tab_list(tabList) 

1139 if common_prefix: 

1140 if 0: 

1141 # This is a bit *too* helpful. 

1142 # It's too easy to type ahead by mistake. 

1143 # Instead, completion should happen only when the user types <tab>. 

1144 self.set_label(common_prefix) 

1145 # Recompute the tab list. 

1146 common_prefix, tabList = self.compute_tab_list() 

1147 self.show_tab_list(tabList) 

1148 if len(tabList) == 1: 

1149 # Automatically complete the typing only if there is only one item in the list. 

1150 self.set_label(common_prefix) 

1151 else: 

1152 # Restore everything. 

1153 self.set_label(self.get_label()[:-1]) 

1154 self.extend_label(char) 

1155 #@+node:ekr.20140813052702.18199: *3* fnc.do_tab 

1156 def do_tab(self): 

1157 """Handle tab completion.""" 

1158 old = self.get_label() 

1159 common_prefix, tabList = self.compute_tab_list() 

1160 self.show_tab_list(tabList) 

1161 if len(tabList) == 1: 

1162 common_prefix = tabList[0] 

1163 self.set_label(common_prefix) 

1164 elif len(common_prefix) > len(old): 

1165 self.set_label(common_prefix) 

1166 #@+node:ekr.20140813052702.18200: *3* fnc.get_file_name (entry) 

1167 def get_file_name(self, event, callback, filterExt, prompt, tabName): 

1168 """Get a file name, supporting file completion.""" 

1169 c, k = self.c, self.c.k 

1170 tag = 'get-file-name' 

1171 state = k.getState(tag) 

1172 char = event.char if event else '' 

1173 if state == 0: 

1174 # Re-init all ivars. 

1175 self.log = c.frame.log or g.NullObject() 

1176 self.callback = callback 

1177 self.filterExt = filterExt or ['.pyc', '.bin',] 

1178 self.prompt = prompt 

1179 self.tabName = tabName 

1180 join = g.os_path_finalize_join 

1181 finalize = g.os_path_finalize 

1182 normslashes = g.os_path_normslashes 

1183 # #467: Add setting for preferred directory. 

1184 directory = c.config.getString('initial-chooser-directory') 

1185 if directory: 

1186 directory = finalize(directory) 

1187 if not g.os_path_exists(directory): 

1188 g.es_print('@string initial-chooser-directory not found', 

1189 normslashes(directory)) 

1190 directory = None 

1191 if not directory: 

1192 directory = finalize(os.curdir) 

1193 # Init the label and state. 

1194 tail = k.functionTail and k.functionTail.strip() 

1195 label = join(directory, tail) if tail else directory + os.sep 

1196 self.set_label(normslashes(label)) 

1197 k.setState(tag, 1, self.get_file_name) 

1198 self.log.selectTab(self.tabName) 

1199 junk, tabList = self.compute_tab_list() 

1200 self.show_tab_list(tabList) 

1201 c.minibufferWantsFocus() 

1202 elif char == 'Escape': 

1203 k.keyboardQuit() 

1204 elif char in ('\n', 'Return'): 

1205 self.log.deleteTab(self.tabName) 

1206 path = self.get_label() 

1207 k.keyboardQuit() 

1208 if self.callback: 

1209 # pylint: disable=not-callable 

1210 self.callback(path) 

1211 else: 

1212 g.trace('no callback') 

1213 elif char in ('\t', 'Tab'): 

1214 self.do_tab() 

1215 c.minibufferWantsFocus() 

1216 elif char in ('\b', 'BackSpace'): 

1217 self.do_back_space() 

1218 c.minibufferWantsFocus() 

1219 elif k.isPlainKey(char): 

1220 self.do_char(char) 

1221 else: 

1222 pass 

1223 #@+node:ekr.20140813052702.18201: *3* fnc.extend/get/set_label 

1224 def extend_label(self, s): 

1225 """Extend the label by s.""" 

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

1227 

1228 def get_label(self): 

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

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

1231 

1232 def set_label(self, s): 

1233 """Set the label after the prompt to s. The prompt never changes.""" 

1234 self.c.k.setLabel(self.prompt, protect=True) 

1235 self.c.k.extendLabel(s or '', select=False, protect=False) 

1236 #@+node:ekr.20140813052702.18202: *3* fnc.show_tab_list 

1237 def show_tab_list(self, tabList): 

1238 """Show the tab list in the log tab.""" 

1239 self.log.clearTab(self.tabName) 

1240 s = g.os_path_finalize(os.curdir) + os.sep 

1241 es = [] 

1242 for path in tabList: 

1243 theDir, fileName = g.os_path_split(path) 

1244 s = theDir if path.endswith(os.sep) else fileName 

1245 s = fileName or g.os_path_basename(theDir) + os.sep 

1246 es.append(s) 

1247 g.es('', '\n'.join(es), tabName=self.tabName) 

1248 #@-others 

1249#@+node:ekr.20140816165728.18940: ** class GetArg 

1250class GetArg: 

1251 """ 

1252 A class encapsulating all k.getArg logic. 

1253 

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

1255 

1256 For details, see the docstring for ga.get_arg 

1257 """ 

1258 #@+others 

1259 #@+node:ekr.20140818052417.18241: *3* ga.birth 

1260 #@+node:ekr.20140816165728.18952: *4* ga.__init__ 

1261 def __init__(self, c, prompt='full-command: ', tabName='Completion'): 

1262 """Ctor for GetArg class.""" 

1263 # Common ivars. 

1264 self.c = c 

1265 self.k = c.k 

1266 assert c 

1267 assert c.k 

1268 self.log = c.frame.log or g.NullObject() 

1269 self.tabName = tabName 

1270 # State vars. 

1271 self.after_get_arg_state = None, None, None 

1272 self.arg_completion = True 

1273 self.handler = None 

1274 self.tabList = [] 

1275 # Tab cycling ivars... 

1276 self.cycling_prefix = None 

1277 self.cycling_index = -1 

1278 self.cycling_tabList = [] 

1279 # The following are k globals. 

1280 # k.arg. 

1281 # k.argSelectedText 

1282 # k.oneCharacterArg 

1283 #@+node:ekr.20140817110228.18321: *3* ga.compute_tab_list 

1284 # Called from k.doTabCompletion: with tabList = list(c.commandsDict.keys()) 

1285 

1286 def compute_tab_list(self, tabList): 

1287 """Compute and show the available completions.""" 

1288 # Support vim-mode commands. 

1289 command = self.get_label() 

1290 if self.is_command(command): 

1291 tabList, common_prefix = g.itemsMatchingPrefixInList(command, tabList) 

1292 return common_prefix, tabList 

1293 # 

1294 # For now, disallow further completions if something follows the command. 

1295 command = self.get_command(command) 

1296 return command, [command] 

1297 #@+node:ekr.20140816165728.18965: *3* ga.do_back_space (entry) 

1298 # Called from k.fullCommand: with defaultTabList = list(c.commandsDict.keys()) 

1299 

1300 def do_back_space(self, tabList, completion=True): 

1301 """Handle a backspace and update the completion list.""" 

1302 k = self.k 

1303 self.tabList = tabList[:] if tabList else [] 

1304 # Update the label. 

1305 w = k.w 

1306 i, j = w.getSelectionRange() 

1307 ins = w.getInsertPoint() 

1308 if ins > len(k.mb_prefix): 

1309 # Step 1: actually delete the character. 

1310 i, j = w.getSelectionRange() 

1311 if i == j: 

1312 ins -= 1 

1313 w.delete(ins) 

1314 w.setSelectionRange(ins, ins, insert=ins) 

1315 else: 

1316 ins = i 

1317 w.delete(i, j) 

1318 w.setSelectionRange(i, i, insert=ins) 

1319 if w.getAllText().strip(): 

1320 junk, tabList = self.compute_tab_list(self.tabList) 

1321 # Do *not* extend the label to the common prefix. 

1322 else: 

1323 tabList = [] 

1324 if completion: 

1325 # #323. 

1326 common_prefix, tabList = self.compute_tab_list(tabList) 

1327 self.show_tab_list(tabList) 

1328 self.reset_tab_cycling() 

1329 #@+node:ekr.20140817110228.18323: *3* ga.do_tab (entry) & helpers 

1330 # Used by ga.get_arg and k.fullCommand. 

1331 

1332 def do_tab(self, tabList, completion=True): 

1333 """Handle tab completion when the user hits a tab.""" 

1334 c = self.c 

1335 if completion: 

1336 tabList = self.tabList = tabList[:] if tabList else [] 

1337 common_prefix, tabList = self.compute_tab_list(tabList) 

1338 if self.cycling_prefix and not self.cycling_prefix.startswith(common_prefix): 

1339 self.cycling_prefix = common_prefix 

1340 # 

1341 # No tab cycling for completed commands having 

1342 # a 'tab_callback' attribute. 

1343 if len(tabList) == 1 and self.do_tab_callback(): 

1344 return 

1345 # #323: *Always* call ga.do_tab_list. 

1346 self.do_tab_cycling(common_prefix, tabList) 

1347 c.minibufferWantsFocus() 

1348 #@+node:ekr.20140818145250.18235: *4* ga.do_tab_callback 

1349 def do_tab_callback(self): 

1350 """ 

1351 If the command-name handler has a tab_callback, 

1352 call handler.tab_callback() and return True. 

1353 """ 

1354 c, k = self.c, self.k 

1355 commandName, tail = k.getMinibufferCommandName() 

1356 handler = c.commandsDict.get(commandName) 

1357 if hasattr(handler, 'tab_callback'): 

1358 self.reset_tab_cycling() 

1359 k.functionTail = tail # For k.getFileName. 

1360 handler.tab_callback() 

1361 return True 

1362 return False 

1363 #@+node:ekr.20140819050118.18317: *4* ga.do_tab_cycling 

1364 def do_tab_cycling(self, common_prefix, tabList): 

1365 """Put the next (or first) completion in the minibuffer.""" 

1366 s = self.get_label() 

1367 if not common_prefix: 

1368 # Leave the minibuffer as it is. 

1369 self.show_tab_list(tabList) 

1370 # #323. 

1371 elif ( 

1372 self.cycling_prefix and 

1373 s.startswith(self.cycling_prefix) and 

1374 sorted(self.cycling_tabList) == sorted(tabList) # Bug fix: 2016/10/14 

1375 ): 

1376 n = self.cycling_index 

1377 n = self.cycling_index = n + 1 if n + 1 < len(self.cycling_tabList) else 0 

1378 self.set_label(self.cycling_tabList[n]) 

1379 self.show_tab_list(self.cycling_tabList) 

1380 else: 

1381 # Restart. 

1382 self.show_tab_list(tabList) 

1383 self.cycling_tabList = tabList[:] 

1384 self.cycling_prefix = common_prefix 

1385 self.set_label(common_prefix) 

1386 if tabList and common_prefix == tabList[0]: 

1387 self.cycling_index = 0 

1388 else: 

1389 self.cycling_index = -1 

1390 #@+node:ekr.20140819050118.18318: *4* ga.reset_tab_cycling 

1391 def reset_tab_cycling(self): 

1392 """Reset all tab cycling ivars.""" 

1393 self.cycling_prefix = None 

1394 self.cycling_index = -1 

1395 self.cycling_tabList = [] 

1396 #@+node:ekr.20140816165728.18958: *3* ga.extend/get/set_label 

1397 # Not useful because k.entendLabel doesn't handle selected text. 

1398 

1399 if 0: 

1400 

1401 def extend_label(self, s): 

1402 """Extend the label by s.""" 

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

1404 

1405 def get_label(self): 

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

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

1408 

1409 def set_label(self, s): 

1410 """Set the label after the prompt to s. The prompt never changes.""" 

1411 k = self.c.k 

1412 # Using k.mb_prefix is simplest. No ga.ivars need be inited. 

1413 k.setLabel(k.mb_prefix, protect=True) 

1414 k.extendLabel(s or '', select=False, protect=False) 

1415 #@+node:ekr.20140816165728.18941: *3* ga.get_arg (entry) & helpers 

1416 def get_arg(self, event, 

1417 returnKind=None, returnState=None, handler=None, 

1418 tabList=None, completion=True, oneCharacter=False, 

1419 stroke=None, useMinibuffer=True 

1420 ): 

1421 #@+<< ga.get_arg docstring >> 

1422 #@+node:ekr.20140822051549.18299: *4* << ga.get_arg docstring >> 

1423 """ 

1424 Accumulate an argument. Enter the given return state when done. 

1425 

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

1427 

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

1429 

1430 The arguments to ga.get_arg are as follows: 

1431 

1432 event: The event passed to the command. 

1433 

1434 returnKind=None: A string. 

1435 returnState=None, An int. 

1436 handler=None, A function. 

1437 

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

1439 

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

1441 

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

1443 

1444 completion=True: True if completions are enabled. 

1445 

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

1447 

1448 stroke=None: The incoming key stroke. 

1449 

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

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

1452 

1453 """ 

1454 #@-<< ga.get_arg docstring >> 

1455 if tabList is None: 

1456 tabList = [] 

1457 c, k = self.c, self.k 

1458 state = k.getState('getArg') 

1459 c.check_event(event) 

1460 c.minibufferWantsFocusNow() 

1461 char = event.char if event else '' 

1462 if state == 0: 

1463 self.do_state_zero(completion, event, handler, oneCharacter, 

1464 returnKind, returnState, tabList, useMinibuffer) 

1465 return 

1466 if char == 'Escape': 

1467 k.keyboardQuit() 

1468 elif self.should_end(char, stroke): 

1469 self.do_end(event, char, stroke) 

1470 elif char in ('\t', 'Tab'): 

1471 self.do_tab(self.tabList, self.arg_completion) 

1472 elif char in ('\b', 'BackSpace'): 

1473 self.do_back_space(self.tabList, self.arg_completion) 

1474 c.minibufferWantsFocus() 

1475 elif k.isFKey(stroke): 

1476 # Ignore only F-keys. Ignoring all except plain keys would kill unicode searches. 

1477 pass 

1478 else: 

1479 self.do_char(event, char) 

1480 #@+node:ekr.20161019060054.1: *4* ga.cancel_after_state 

1481 def cancel_after_state(self): 

1482 

1483 self.after_get_arg_state = None 

1484 #@+node:ekr.20140816165728.18955: *4* ga.do_char 

1485 def do_char(self, event, char): 

1486 """Handle a non-special character.""" 

1487 k = self.k 

1488 k.updateLabel(event) 

1489 # Any plain key resets tab cycling. 

1490 self.reset_tab_cycling() 

1491 #@+node:ekr.20140817110228.18316: *4* ga.do_end 

1492 def do_end(self, event, char, stroke): 

1493 """A return or escape has been seen.""" 

1494 k = self.k 

1495 if char == '\t' and char in k.getArgEscapes: 

1496 k.getArgEscapeFlag = True 

1497 if stroke and stroke in k.getArgEscapes: 

1498 k.getArgEscapeFlag = True 

1499 # Get the value. 

1500 gui_arg = getattr(g.app.gui, 'curses_gui_arg', None) 

1501 if k.oneCharacterArg: 

1502 k.arg = char 

1503 else: 

1504 # A hack to support the curses gui. 

1505 k.arg = gui_arg or self.get_label() 

1506 kind, n, handler = self.after_get_arg_state 

1507 if kind: 

1508 k.setState(kind, n, handler) 

1509 self.log.deleteTab('Completion') 

1510 self.reset_tab_cycling() 

1511 if handler: 

1512 # pylint: disable=not-callable 

1513 handler(event) 

1514 #@+node:ekr.20140817110228.18317: *4* ga.do_state_zero 

1515 def do_state_zero(self, completion, event, handler, oneCharacter, 

1516 returnKind, returnState, tabList, useMinibuffer 

1517 ): 

1518 """Do state 0 processing.""" 

1519 c, k = self.c, self.k 

1520 # 

1521 # Set the ga globals... 

1522 k.getArgEscapeFlag = False 

1523 self.after_get_arg_state = returnKind, returnState, handler 

1524 self.arg_completion = completion 

1525 self.cycling_prefix = None 

1526 self.handler = handler 

1527 self.tabList = tabList[:] if tabList else [] 

1528 # 

1529 # Set the k globals... 

1530 k.functionTail = None 

1531 k.oneCharacterArg = oneCharacter 

1532 # 

1533 # Do *not* change the label here! 

1534 # Enter the next state. 

1535 c.widgetWantsFocus(c.frame.body.wrapper) 

1536 k.setState('getArg', 1, k.getArg) 

1537 # pylint: disable=consider-using-ternary 

1538 k.afterArgWidget = event and event.widget or c.frame.body.wrapper 

1539 if useMinibuffer: 

1540 c.minibufferWantsFocus() 

1541 #@+node:ekr.20140818103808.18234: *4* ga.should_end 

1542 def should_end(self, char, stroke): 

1543 """Return True if ga.get_arg should return.""" 

1544 k = self.k 

1545 return ( 

1546 char in ('\n', 'Return',) or 

1547 k.oneCharacterArg or 

1548 stroke and stroke in k.getArgEscapes or 

1549 char == '\t' and char in k.getArgEscapes 

1550 # The Find Easter Egg. 

1551 ) 

1552 #@+node:ekr.20140818103808.18235: *4* ga.trace_state 

1553 def trace_state(self, char, completion, handler, state, stroke): 

1554 """Trace the vars and ivars.""" 

1555 k = self.c.k 

1556 g.trace( 

1557 'state', state, 'char', repr(char), 'stroke', repr(stroke), 

1558 # 'isPlain',k.isPlainKey(stroke), 

1559 '\n', 

1560 'escapes', k.getArgEscapes, 

1561 'completion', self.arg_completion, 

1562 'handler', self.handler and self.handler.__name__ or 'None', 

1563 ) 

1564 #@+node:ekr.20140818074502.18222: *3* ga.get_command 

1565 def get_command(self, s): 

1566 """Return the command part of a minibuffer contents s.""" 

1567 # #1121. 

1568 if ' ' in s: 

1569 return s[: s.find(' ')].strip() 

1570 return s 

1571 #@+node:ekr.20140818085719.18227: *3* ga.get_minibuffer_command_name 

1572 def get_minibuffer_command_name(self): 

1573 """Return the command name in the minibuffer.""" 

1574 s = self.get_label() 

1575 command = self.get_command(s) 

1576 tail = s[len(command) :] 

1577 return command, tail 

1578 #@+node:ekr.20140818074502.18221: *3* ga.is_command 

1579 def is_command(self, s): 

1580 """Return False if something, even a blank, follows a command.""" 

1581 # #1121: only ascii space terminates a command. 

1582 return ' ' not in s 

1583 #@+node:ekr.20140816165728.18959: *3* ga.show_tab_list & helper 

1584 def show_tab_list(self, tabList): 

1585 """Show the tab list in the log tab.""" 

1586 k = self.k 

1587 self.log.clearTab(self.tabName) 

1588 d = k.computeInverseBindingDict() 

1589 data, legend, n = [], False, 0 

1590 for commandName in tabList: 

1591 dataList = d.get(commandName, []) 

1592 if dataList: 

1593 for z in dataList: 

1594 pane, key = z 

1595 s1a = '' if pane in ('all:', 'button:') else f"{pane} " 

1596 s1b = k.prettyPrintKey(key) 

1597 s1 = s1a + s1b 

1598 s2 = self.command_source(commandName) 

1599 if s2 != ' ': 

1600 legend = True 

1601 s3 = commandName 

1602 data.append((s1, s2, s3),) 

1603 n = max(n, len(s1)) 

1604 else: 

1605 # Bug fix: 2017/03/26 

1606 data.append(('', ' ', commandName),) 

1607 aList = ['%*s %s %s' % (-n, z1, z2, z3) for z1, z2, z3 in data] 

1608 if legend: 

1609 aList.extend([ 

1610 '', 

1611 'legend:', 

1612 'G leoSettings.leo', 

1613 'M myLeoSettings.leo', 

1614 'L local .leo File', 

1615 ]) 

1616 g.es('', '\n'.join(aList), tabName=self.tabName) 

1617 #@+node:ekr.20150402034643.1: *4* ga.command_source 

1618 def command_source(self, commandName): 

1619 """ 

1620 Return the source legend of an @button/@command node. 

1621 'G' leoSettings.leo 

1622 'M' myLeoSettings.leo 

1623 'L' local .leo File 

1624 ' ' not an @command or @button node 

1625 """ 

1626 c = self.c 

1627 if commandName.startswith('@'): 

1628 d = c.commandsDict 

1629 func = d.get(commandName) 

1630 if hasattr(func, 'source_c'): 

1631 c2 = func.source_c 

1632 fn2 = c2.shortFileName().lower() 

1633 if fn2.endswith('myleosettings.leo'): 

1634 return 'M' 

1635 if fn2.endswith('leosettings.leo'): 

1636 return 'G' 

1637 return 'L' 

1638 return '?' 

1639 return ' ' 

1640 #@-others 

1641#@+node:ekr.20061031131434.74: ** class KeyHandlerClass 

1642class KeyHandlerClass: 

1643 """ 

1644 A class to support emacs-style commands. 

1645 c.k is an instance of this class. 

1646 """ 

1647 #@+others 

1648 #@+node:ekr.20061031131434.75: *3* k.Birth 

1649 #@+node:ekr.20061031131434.76: *4* k.__init__& helpers 

1650 def __init__(self, c): 

1651 """Create a key handler for c.""" 

1652 self.c = c 

1653 self.dispatchEvent = None 

1654 self.fnc = None # A singleton defined in k.finishCreate. 

1655 self.getArgInstance = None # A singleton defined in k.finishCreate. 

1656 self.inited = False # Set at end of finishCreate. 

1657 self.killedBindings = [] # A list of commands whose bindings have been set to None in the local file. 

1658 self.replace_meta_with_alt = False # True: (Mac only) swap Meta and Alt keys. 

1659 self.w = None # Will be None for NullGui. 

1660 # Generalize... 

1661 self.x_hasNumeric = ['sort-lines', 'sort-fields'] 

1662 self.altX_prompt = 'full-command: ' 

1663 # Access to data types defined in leoKeys.py 

1664 self.KeyStroke = g.KeyStroke 

1665 # Define all ivars... 

1666 self.defineExternallyVisibleIvars() 

1667 self.defineInternalIvars() 

1668 self.reloadSettings() 

1669 self.defineSingleLineCommands() 

1670 self.defineMultiLineCommands() 

1671 self.autoCompleter = AutoCompleterClass(self) 

1672 self.qcompleter = None # Set by AutoCompleter.start. 

1673 self.setDefaultUnboundKeyAction() 

1674 self.setDefaultEditingAction() 

1675 #@+node:ekr.20061031131434.78: *5* k.defineExternallyVisibleIvars 

1676 def defineExternallyVisibleIvars(self): 

1677 

1678 self.abbrevOn = False # True: abbreviations are on. 

1679 self.arg = '' # The value returned by k.getArg. 

1680 self.getArgEscapeFlag = False # True: the user escaped getArg in an unusual way. 

1681 self.getArgEscapes = [] 

1682 self.inputModeName = '' # The name of the input mode, or None. 

1683 self.modePrompt = '' # The mode promopt. 

1684 self.state = g.bunch(kind=None, n=None, handler=None) 

1685 

1686 # Remove ??? 

1687 self.givenArgs = [] # Args specified after the command name in k.simulateCommand. 

1688 self.functionTail = None # For commands that take minibuffer arguments. 

1689 #@+node:ekr.20061031131434.79: *5* k.defineInternalIvars 

1690 def defineInternalIvars(self): 

1691 """Define internal ivars of the KeyHandlerClass class.""" 

1692 self.abbreviationsDict = {} # Abbreviations created by @alias nodes. 

1693 # Previously defined bindings... 

1694 self.bindingsDict = {} # Keys are Tk key names, values are lists of BindingInfo objects. 

1695 # Previously defined binding tags. 

1696 self.bindtagsDict = {} # Keys are strings (the tag), values are 'True' 

1697 self.commandHistory = [] 

1698 # Up arrow will select commandHistory[commandIndex] 

1699 self.commandIndex = 0 # List/stack of previously executed commands. 

1700 # Keys are scope names: 'all','text',etc. or mode names. 

1701 # Values are dicts: keys are strokes, values are BindingInfo objects. 

1702 self.masterBindingsDict = {} 

1703 self.masterGuiBindingsDict = {} # Keys are strokes; value is True. 

1704 # Special bindings for k.fullCommand... 

1705 self.mb_copyKey = None 

1706 self.mb_pasteKey = None 

1707 self.mb_cutKey = None 

1708 # Keys whose bindings are computed by initSpecialIvars... 

1709 self.abortAllModesKey = None 

1710 self.autoCompleteForceKey = None 

1711 self.demoNextKey = None # New support for the demo.py plugin. 

1712 self.demoPrevKey = None # New support for the demo.py plugin. 

1713 # Used by k.masterKeyHandler... 

1714 self.stroke = None 

1715 self.mb_event = None 

1716 self.mb_history = [] 

1717 self.mb_help = False 

1718 self.mb_helpHandler = None 

1719 # Important: these are defined in k.defineExternallyVisibleIvars... 

1720 # self.getArgEscapes = [] 

1721 # self.getArgEscapeFlag 

1722 # For onIdleTime... 

1723 self.idleCount = 0 

1724 # For modes... 

1725 self.modeBindingsDict = {} 

1726 self.modeWidget = None 

1727 self.silentMode = False 

1728 #@+node:ekr.20080509064108.7: *5* k.defineMultiLineCommands 

1729 def defineMultiLineCommands(self): 

1730 k = self 

1731 k.multiLineCommandList = [ 

1732 # EditCommandsClass 

1733 'add-space-to-lines', 

1734 'add-tab-to-lines', 

1735 'back-page', 

1736 'back-page-extend-selection', 

1737 'back-paragraph', 

1738 'back-paragraph-extend-selection', 

1739 'back-sentence', 

1740 'back-sentence-extend-selection', 

1741 'backward-kill-paragraph', 

1742 'beginning-of-buffer', 

1743 'beginning-of-buffer-extend-selection', 

1744 'center-line', 

1745 'center-region', 

1746 'clean-all-lines', 

1747 'clean-lines', 

1748 'downcase-region', 

1749 'end-of-buffer', 

1750 'end-of-buffer-extend-selection', 

1751 'extend-to-paragraph', 

1752 'extend-to-sentence', 

1753 'fill-paragraph', 

1754 'fill-region', 

1755 'fill-region-as-paragraph', 

1756 'flush-lines', 

1757 'forward-page', 

1758 'forward-page-extend-selection', 

1759 'forward-paragraph', 

1760 'forward-paragraph-extend-selection', 

1761 'forward-sentence', 

1762 'forward-sentence-extend-selection', 

1763 'indent-relative', 

1764 'indent-rigidly', 

1765 'indent-to-comment-column', 

1766 'move-lines-down', 

1767 'move-lines-up', 

1768 'next-line', 

1769 'next-line-extend-selection', 

1770 'previous-line', 

1771 'previous-line-extend-selection', 

1772 'remove-blank-lines', 

1773 'remove-space-from-lines', 

1774 'remove-tab-from-lines', 

1775 'reverse-region', 

1776 'reverse-sort-lines', 

1777 'reverse-sort-lines-ignoring-case', 

1778 'scroll-down-half-page', 

1779 'scroll-down-line', 

1780 'scroll-down-page', 

1781 'scroll-up-half-page', 

1782 'scroll-up-line', 

1783 'scroll-up-page', 

1784 'simulate-begin-drag', 

1785 'simulate-end-drag', 

1786 'sort-columns', 

1787 'sort-fields', 

1788 'sort-lines', 

1789 'sort-lines-ignoring-case', 

1790 'split-line', 

1791 'tabify', 

1792 'transpose-lines', 

1793 'untabify', 

1794 'upcase-region', 

1795 # KeyHandlerCommandsClass 

1796 'repeat-complex-command', 

1797 # KillBufferCommandsClass 

1798 'backward-kill-sentence', 

1799 'kill-sentence', 

1800 'kill-region', 

1801 'kill-region-save', 

1802 # QueryReplaceCommandsClass 

1803 'query-replace', 

1804 'query-replace-regex', 

1805 # RectangleCommandsClass 

1806 'clear-rectangle', 

1807 'close-rectangle', 

1808 'delete-rectangle', 

1809 'kill-rectangle', 

1810 'open-rectangle', 

1811 'string-rectangle', 

1812 'yank-rectangle', 

1813 # SearchCommandsClass 

1814 'change', 

1815 'change-then-find', 

1816 'find-next', 

1817 'find-prev', 

1818 ] 

1819 #@+node:ekr.20080509064108.6: *5* k.defineSingleLineCommands 

1820 def defineSingleLineCommands(self): 

1821 k = self 

1822 # These commands can be executed in the minibuffer. 

1823 k.singleLineCommandList = [ 

1824 # EditCommandsClass 

1825 'back-to-indentation', 

1826 'back-to-home', # 2010/02/01 

1827 'back-char', 

1828 'back-char-extend-selection', 

1829 'back-word', 

1830 'back-word-extend-selection', 

1831 'backward-delete-char', 

1832 'backward-find-character', 

1833 'backward-find-character-extend-selection', 

1834 'beginning-of-line', 

1835 'beginning-of-line-extend-selection', 

1836 'capitalize-word', 

1837 'delete-char', 

1838 'delete-indentation', 

1839 'delete-spaces', 

1840 'downcase-word', 

1841 'end-of-line', 

1842 'end-of-line-extend-selection', 

1843 'escape', 

1844 'exchange-point-mark', 

1845 'extend-to-line', 

1846 'extend-to-word', 

1847 'find-character', 

1848 'find-character-extend-selection', 

1849 'find-word', 

1850 'find-word-in-line', 

1851 'forward-char', 

1852 'forward-char-extend-selection', 

1853 'forward-end-word', 

1854 'forward-end-word-extend-selection', 

1855 'forward-word', 

1856 'forward-word-extend-selection', 

1857 'insert-newline', 

1858 'insert-parentheses', 

1859 'move-past-close', 

1860 'move-past-close-extend-selection', 

1861 'newline-and-indent', 

1862 'select-all', 

1863 'transpose-chars', 

1864 'transpose-words', 

1865 'upcase-word', 

1866 # LeoFind class. 

1867 'start-search', # #2041. 

1868 # KeyHandlerCommandsClass 

1869 'full-command', # #2041. 

1870 # 'auto-complete', 

1871 # 'negative-argument', 

1872 # 'number-command', 

1873 # 'number-command-0', 

1874 # 'number-command-1', 

1875 # 'number-command-2', 

1876 # 'number-command-3', 

1877 # 'number-command-4', 

1878 # 'number-command-5', 

1879 # 'number-command-6', 

1880 # 'number-command-7', 

1881 # 'number-command-8', 

1882 # 'universal-argument', 

1883 # KillBufferCommandsClass 

1884 'backward-kill-word', 

1885 'kill-line', 

1886 'kill-word', 

1887 'kill-ws', 

1888 'yank', 

1889 'yank-pop', 

1890 'zap-to-character', 

1891 # leoCommands 

1892 'cut-text', 

1893 'copy-text', 

1894 'paste-text', 

1895 # MacroCommandsClass 

1896 'call-last-kbd-macro', 

1897 # search commands 

1898 # 'replace-string', # A special case so Shift-Ctrl-r will work after Ctrl-f. 

1899 'set-find-everywhere', # 2011/06/07 

1900 'set-find-node-only', # 2011/06/07 

1901 'set-find-suboutline-only', # 2011/06/07 

1902 'toggle-find-collapses_nodes', 

1903 'toggle-find-ignore-case-option', 

1904 'toggle-find-in-body-option', 

1905 'toggle-find-in-headline-option', 

1906 'toggle-find-mark-changes-option', 

1907 'toggle-find-mark-finds-option', 

1908 'toggle-find-regex-option', 

1909 'toggle-find-reverse-option', 

1910 'toggle-find-word-option', 

1911 'toggle-find-wrap-around-option', 

1912 ] 

1913 #@+node:ekr.20061031131434.80: *4* k.finishCreate 

1914 def finishCreate(self): 

1915 """ 

1916 Complete the construction of the keyHandler class. 

1917 c.commandsDict has been created when this is called. 

1918 """ 

1919 c, k = self.c, self 

1920 k.w = c.frame.miniBufferWidget # Will be None for NullGui. 

1921 k.fnc = FileNameChooser(c) # A singleton. Defined here so that c.k will exist. 

1922 k.getArgInstance = GetArg(c) # A singleton. Defined here so that c.k will exist. 

1923 # Important: This must be called this now, 

1924 # even though LM.load calls g.app.makeAllBindings later. 

1925 k.makeAllBindings() 

1926 k.initCommandHistory() 

1927 k.inited = True 

1928 k.setDefaultInputState() 

1929 k.resetLabel() 

1930 #@+node:ekr.20061101071425: *4* k.oops 

1931 def oops(self): 

1932 

1933 g.trace('Should be defined in subclass:', g.callers(4)) 

1934 #@+node:ekr.20120217070122.10479: *4* k.reloadSettings 

1935 def reloadSettings(self): 

1936 # Part 1: These were in the ctor. 

1937 c = self.c 

1938 getBool = c.config.getBool 

1939 getColor = c.config.getColor 

1940 self.enable_autocompleter = getBool('enable-autocompleter-initially') 

1941 self.enable_calltips = getBool('enable-calltips-initially') 

1942 self.ignore_unbound_non_ascii_keys = getBool('ignore-unbound-non-ascii-keys') 

1943 self.minibuffer_background_color = getColor( 

1944 'minibuffer-background-color') or 'lightblue' 

1945 self.minibuffer_foreground_color = getColor( 

1946 'minibuffer-foreground-color') or 'black' 

1947 self.minibuffer_warning_color = getColor( 

1948 'minibuffer-warning-color') or 'lightgrey' 

1949 self.minibuffer_error_color = getColor('minibuffer-error-color') or 'red' 

1950 self.replace_meta_with_alt = getBool('replace-meta-with-alt') 

1951 self.warn_about_redefined_shortcuts = getBool('warn-about-redefined-shortcuts') 

1952 # Has to be disabled (default) for AltGr support on Windows 

1953 self.enable_alt_ctrl_bindings = c.config.getBool('enable-alt-ctrl-bindings') 

1954 # Part 2: These were in finishCreate. 

1955 # Set mode colors used by k.setInputState. 

1956 bg = c.config.getColor('body-text-background-color') or 'white' 

1957 fg = c.config.getColor('body-text-foreground-color') or 'black' 

1958 self.command_mode_bg_color = getColor('command-mode-bg-color') or bg 

1959 self.command_mode_fg_color = getColor('command-mode-fg-color') or fg 

1960 self.insert_mode_bg_color = getColor('insert-mode-bg-color') or bg 

1961 self.insert_mode_fg_color = getColor('insert-mode-fg-color') or fg 

1962 self.overwrite_mode_bg_color = getColor('overwrite-mode-bg-color') or bg 

1963 self.overwrite_mode_fg_color = getColor('overwrite-mode-fg-color') or fg 

1964 self.unselected_body_bg_color = getColor('unselected-body-bg-color') or bg 

1965 self.unselected_body_fg_color = getColor('unselected-body-fg-color') or bg 

1966 #@+node:ekr.20110209093958.15413: *4* k.setDefaultEditingKeyAction 

1967 def setDefaultEditingAction(self): 

1968 c = self.c 

1969 action = c.config.getString('default-editing-state') or 'insert' 

1970 action.lower() 

1971 if action not in ('command', 'insert', 'overwrite'): 

1972 g.trace(f"ignoring default_editing_state: {action}") 

1973 action = 'insert' 

1974 self.defaultEditingAction = action 

1975 #@+node:ekr.20061031131434.82: *4* k.setDefaultUnboundKeyAction 

1976 def setDefaultUnboundKeyAction(self, allowCommandState=True): 

1977 c, k = self.c, self 

1978 defaultAction = c.config.getString('top-level-unbound-key-action') or 'insert' 

1979 defaultAction.lower() 

1980 if defaultAction == 'command' and not allowCommandState: 

1981 self.unboundKeyAction = 'insert' 

1982 elif defaultAction in ('command', 'insert', 'overwrite'): 

1983 self.unboundKeyAction = defaultAction 

1984 else: 

1985 g.trace(f"ignoring top_level_unbound_key_action setting: {defaultAction}") 

1986 self.unboundKeyAction = 'insert' 

1987 self.defaultUnboundKeyAction = self.unboundKeyAction 

1988 k.setInputState(self.defaultUnboundKeyAction) 

1989 #@+node:ekr.20061031131434.88: *3* k.Binding 

1990 #@+node:ekr.20061031131434.89: *4* k.bindKey & helpers 

1991 def bindKey(self, pane, shortcut, callback, commandName, modeFlag=False, tag=None): 

1992 """ 

1993 Bind the indicated shortcut (a Tk keystroke) to the callback. 

1994 

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

1996 and k.bindingsDict. 

1997 

1998 tag gives the source of the binding. 

1999 

2000 Return True if the binding was made successfully. 

2001 """ 

2002 k = self 

2003 if not shortcut: 

2004 # Don't use this method to undo bindings. 

2005 return False 

2006 if not k.check_bind_key(commandName, pane, shortcut): 

2007 return False 

2008 aList = k.bindingsDict.get(shortcut, []) 

2009 try: 

2010 if not shortcut: 

2011 stroke = None 

2012 elif g.isStroke(shortcut): 

2013 stroke = shortcut 

2014 assert stroke.s, stroke 

2015 else: 

2016 assert shortcut, g.callers() 

2017 stroke = g.KeyStroke(binding=shortcut) 

2018 bi = g.BindingInfo( 

2019 kind=tag, 

2020 pane=pane, 

2021 func=callback, 

2022 commandName=commandName, 

2023 stroke=stroke) 

2024 if shortcut: 

2025 k.bindKeyToDict(pane, shortcut, bi) 

2026 # Updates k.masterBindingsDict 

2027 if shortcut and not modeFlag: 

2028 aList = k.remove_conflicting_definitions( 

2029 aList, commandName, pane, shortcut) 

2030 # 2013/03/02: a real bug fix. 

2031 aList.append(bi) 

2032 if shortcut: 

2033 assert stroke 

2034 k.bindingsDict[stroke] = aList 

2035 return True 

2036 except Exception: # Could be a user error. 

2037 if g.unitTesting or not g.app.menuWarningsGiven: 

2038 g.es_print('exception binding', shortcut, 'to', commandName) 

2039 g.print_exception() 

2040 g.app.menuWarningsGiven = True 

2041 return False 

2042 

2043 bindShortcut = bindKey # For compatibility 

2044 #@+node:ekr.20120130074511.10228: *5* k.check_bind_key 

2045 def check_bind_key(self, commandName, pane, stroke): 

2046 """ 

2047 Return True if the binding of stroke to commandName for the given 

2048 pane can be made. 

2049 """ 

2050 # k = self 

2051 assert g.isStroke(stroke) 

2052 # Give warning and return if we try to bind to Enter or Leave. 

2053 for s in ('enter', 'leave'): 

2054 if stroke.lower().find(s) > -1: 

2055 g.warning('ignoring invalid key binding:', f"{commandName} = {stroke}") 

2056 return False 

2057 if pane.endswith('-mode'): 

2058 g.trace('oops: ignoring mode binding', stroke, commandName, g.callers()) 

2059 return False 

2060 return True 

2061 #@+node:ekr.20120130074511.10227: *5* k.kill_one_shortcut 

2062 def kill_one_shortcut(self, stroke): 

2063 """ 

2064 Update the *configuration* dicts so that c.config.getShortcut(name) 

2065 will return None for all names *presently* bound to the stroke. 

2066 """ 

2067 c = self.c 

2068 lm = g.app.loadManager 

2069 if 0: 

2070 # This does not fix 327: Create a way to unbind bindings 

2071 assert stroke in (None, 'None', 'none') or g.isStroke(stroke), repr(stroke) 

2072 else: 

2073 # A crucial shortcut: inverting and uninverting dictionaries is slow. 

2074 # Important: the comparison is valid regardless of the type of stroke. 

2075 if stroke in (None, 'None', 'none'): 

2076 return 

2077 assert g.isStroke(stroke), stroke 

2078 d = c.config.shortcutsDict 

2079 if d is None: 

2080 d = g.TypedDict( # was TypedDictOfLists. 

2081 name='empty shortcuts dict', 

2082 keyType=type('commandName'), 

2083 valType=g.BindingInfo, 

2084 ) 

2085 inv_d = lm.invert(d) 

2086 inv_d[stroke] = [] 

2087 c.config.shortcutsDict = lm.uninvert(inv_d) 

2088 #@+node:ekr.20061031131434.92: *5* k.remove_conflicting_definitions 

2089 def remove_conflicting_definitions(self, aList, commandName, pane, shortcut): 

2090 

2091 k = self 

2092 result = [] 

2093 for bi in aList: 

2094 if pane in ('button', 'all', bi.pane): 

2095 k.kill_one_shortcut(shortcut) 

2096 else: 

2097 result.append(bi) 

2098 return result 

2099 #@+node:ekr.20061031131434.93: *5* k.bindKeyToDict 

2100 def bindKeyToDict(self, pane, stroke, bi): 

2101 """Update k.masterBindingsDict for the stroke.""" 

2102 # New in Leo 4.4.1: Allow redefintions. 

2103 # Called from makeBindingsFromCommandsDict. 

2104 k = self 

2105 assert g.isStroke(stroke), stroke 

2106 d = k.masterBindingsDict.get(pane, {}) 

2107 d[stroke] = bi 

2108 k.masterBindingsDict[pane] = d 

2109 #@+node:ekr.20061031131434.94: *5* k.bindOpenWith 

2110 def bindOpenWith(self, d): 

2111 """Register an open-with command.""" 

2112 c, k = self.c, self 

2113 shortcut = d.get('shortcut') or '' 

2114 name = d.get('name') 

2115 # The first parameter must be event, and it must default to None. 

2116 

2117 def openWithCallback(event=None, c=c, d=d): 

2118 return c.openWith(d=d) 

2119 

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

2121 

2122 commandName = f"open-with-{name.lower()}" 

2123 k.registerCommand( 

2124 allowBinding=True, 

2125 commandName=commandName, 

2126 func=openWithCallback, 

2127 pane='all', 

2128 shortcut=shortcut, 

2129 ) 

2130 #@+node:ekr.20061031131434.95: *4* k.checkBindings 

2131 def checkBindings(self): 

2132 """ 

2133 Print warnings if commands do not have any @shortcut entry. 

2134 The entry may be `None`, of course.""" 

2135 c, k = self.c, self 

2136 if not c.config.getBool('warn-about-missing-settings'): 

2137 return 

2138 for name in sorted(c.commandsDict): 

2139 abbrev = k.abbreviationsDict.get(name) 

2140 key = c.frame.menu.canonicalizeMenuName(abbrev or name) 

2141 key = key.replace('&', '') 

2142 if not c.config.exists(key, 'shortcut'): 

2143 if abbrev: 

2144 g.trace(f"No shortcut for abbrev {name} -> {abbrev} = {key}") 

2145 else: 

2146 g.trace(f"No shortcut for {name} = {key}") 

2147 #@+node:ekr.20061031131434.97: *4* k.completeAllBindings 

2148 def completeAllBindings(self, w=None): 

2149 """ 

2150 Make an actual binding in *all* the standard places. 

2151 

2152 The event will go to k.masterKeyHandler as always, so nothing really changes. 

2153 except that k.masterKeyHandler will know the proper stroke. 

2154 """ 

2155 k = self 

2156 for stroke in k.bindingsDict: 

2157 assert g.isStroke(stroke), repr(stroke) 

2158 k.makeMasterGuiBinding(stroke, w=w) 

2159 #@+node:ekr.20061031131434.96: *4* k.completeAllBindingsForWidget 

2160 def completeAllBindingsForWidget(self, w): 

2161 """Make all a master gui binding for widget w.""" 

2162 k = self 

2163 for stroke in k.bindingsDict: 

2164 assert g.isStroke(stroke), repr(stroke) 

2165 k.makeMasterGuiBinding(stroke, w=w) 

2166 #@+node:ekr.20070218130238: *4* k.dumpMasterBindingsDict 

2167 def dumpMasterBindingsDict(self): 

2168 """Dump k.masterBindingsDict.""" 

2169 k = self 

2170 d = k.masterBindingsDict 

2171 g.pr('\nk.masterBindingsDict...\n') 

2172 for key in sorted(d): 

2173 g.pr(key, '-' * 40) 

2174 d2 = d.get(key) 

2175 for key2 in sorted(d2): 

2176 bi = d2.get(key2) 

2177 g.pr(f"{key2:20} {bi.commandName}") 

2178 #@+node:ekr.20061031131434.99: *4* k.initAbbrev & helper 

2179 def initAbbrev(self): 

2180 c = self.c 

2181 d = c.config.getAbbrevDict() 

2182 if d: 

2183 for key in d: 

2184 commandName = d.get(key) 

2185 if commandName.startswith('press-') and commandName.endswith('-button'): 

2186 pass # Must be done later in k.registerCommand. 

2187 else: 

2188 self.initOneAbbrev(commandName, key) 

2189 #@+node:ekr.20130924035029.12741: *5* k.initOneAbbrev 

2190 def initOneAbbrev(self, commandName, key): 

2191 """Enter key as an abbreviation for commandName in c.commandsDict.""" 

2192 c = self.c 

2193 if c.commandsDict.get(key): 

2194 g.trace('ignoring duplicate abbrev: %s', key) 

2195 else: 

2196 func = c.commandsDict.get(commandName) 

2197 if func: 

2198 c.commandsDict[key] = func 

2199 else: 

2200 g.warning('bad abbrev:', key, 'unknown command name:', commandName) 

2201 #@+node:ekr.20061031131434.101: *4* k.initSpecialIvars 

2202 def initSpecialIvars(self): 

2203 """Set ivars for special keystrokes from previously-existing bindings.""" 

2204 c, k = self.c, self 

2205 warn = c.config.getBool('warn-about-missing-settings') 

2206 for ivar, commandName in ( 

2207 ('abortAllModesKey', 'keyboard-quit'), 

2208 ('autoCompleteForceKey', 'auto-complete-force'), 

2209 ('demoNextKey', 'demo-next'), 

2210 ('demoPrevKey', 'demo-prev'), 

2211 ): 

2212 junk, aList = c.config.getShortcut(commandName) 

2213 aList, found = aList or [], False 

2214 for pane in ('text', 'all'): 

2215 for bi in aList: 

2216 if bi.pane == pane: 

2217 setattr(k, ivar, bi.stroke) 

2218 found = True 

2219 break 

2220 if not found and warn: 

2221 g.trace(f"no setting for {commandName}") 

2222 #@+node:ekr.20061031131434.98: *4* k.makeAllBindings 

2223 def makeAllBindings(self): 

2224 """Make all key bindings in all of Leo's panes.""" 

2225 k = self 

2226 k.bindingsDict = {} 

2227 k.addModeCommands() 

2228 k.makeBindingsFromCommandsDict() 

2229 k.initSpecialIvars() 

2230 k.initAbbrev() 

2231 k.completeAllBindings() 

2232 k.checkBindings() 

2233 #@+node:ekr.20061031131434.102: *4* k.makeBindingsFromCommandsDict 

2234 def makeBindingsFromCommandsDict(self): 

2235 """Add bindings for all entries in c.commandsDict.""" 

2236 c, k = self.c, self 

2237 d = c.commandsDict 

2238 # 

2239 # Step 1: Create d2. 

2240 # Keys are strokes. Values are lists of bi with bi.stroke == stroke. 

2241 d2 = g.TypedDict( # was TypedDictOfLists. 

2242 name='makeBindingsFromCommandsDict helper dict', 

2243 keyType=g.KeyStroke, 

2244 valType=g.BindingInfo, 

2245 ) 

2246 for commandName in sorted(d): 

2247 command = d.get(commandName) 

2248 key, aList = c.config.getShortcut(commandName) 

2249 for bi in aList: 

2250 # Important: bi.stroke is already canonicalized. 

2251 stroke = bi.stroke 

2252 bi.commandName = commandName 

2253 if stroke: 

2254 assert g.isStroke(stroke) 

2255 d2.add_to_list(stroke, bi) 

2256 # 

2257 # Step 2: make the bindings. 

2258 for stroke in sorted(d2.keys()): 

2259 aList2 = d2.get(stroke) 

2260 for bi in aList2: 

2261 commandName = bi.commandName 

2262 command = c.commandsDict.get(commandName) 

2263 tag = bi.kind 

2264 pane = bi.pane 

2265 if stroke and not pane.endswith('-mode'): 

2266 k.bindKey(pane, stroke, command, commandName, tag=tag) 

2267 #@+node:ekr.20061031131434.103: *4* k.makeMasterGuiBinding 

2268 def makeMasterGuiBinding(self, stroke, w=None): 

2269 """Make a master gui binding for stroke in pane w, or in all the standard widgets.""" 

2270 c, k = self.c, self 

2271 f = c.frame 

2272 if w: 

2273 widgets = [w] 

2274 else: 

2275 # New in Leo 4.5: we *must* make the binding in the binding widget. 

2276 bindingWidget = ( 

2277 f.tree 

2278 and hasattr(f.tree, 'bindingWidget') 

2279 and f.tree.bindingWidget 

2280 or None) 

2281 wrapper = f.body and hasattr(f.body, 'wrapper') and f.body.wrapper or None 

2282 canvas = f.tree and hasattr(f.tree, 'canvas') and f.tree.canvas or None 

2283 widgets = [c.miniBufferWidget, wrapper, canvas, bindingWidget] 

2284 for w in widgets: 

2285 if not w: 

2286 continue 

2287 # Make the binding only if no binding for the stroke exists in the widget. 

2288 aList = k.masterGuiBindingsDict.get(stroke, []) 

2289 if w not in aList: 

2290 aList.append(w) 

2291 k.masterGuiBindingsDict[stroke] = aList 

2292 #@+node:ekr.20150402111403.1: *3* k.Command history 

2293 #@+node:ekr.20150402111413.1: *4* k.addToCommandHistory 

2294 def addToCommandHistory(self, commandName): 

2295 """Add a name to the command history.""" 

2296 k = self 

2297 h = k.commandHistory 

2298 if commandName in h: 

2299 h.remove(commandName) 

2300 h.append(commandName) 

2301 k.commandIndex = None 

2302 #@+node:ekr.20150402165918.1: *4* k.commandHistoryDown 

2303 def commandHistoryFwd(self): 

2304 """ 

2305 Move down the Command History - fall off the bottom (return empty string) 

2306 if necessary 

2307 """ 

2308 k = self 

2309 h, i = k.commandHistory, k.commandIndex 

2310 if h: 

2311 commandName = '' 

2312 if i == len(h) - 1: 

2313 # fall off the bottom 

2314 i = None 

2315 elif i is not None: 

2316 # move to next down in list 

2317 i += 1 

2318 commandName = h[i] 

2319 k.commandIndex = i 

2320 k.setLabel(k.mb_prefix + commandName) 

2321 #@+node:ekr.20150402171131.1: *4* k.commandHistoryUp 

2322 def commandHistoryBackwd(self): 

2323 """ 

2324 Return the previous entry in the Command History - stay at the top 

2325 if we are there 

2326 """ 

2327 k = self 

2328 h, i = k.commandHistory, k.commandIndex 

2329 if h: 

2330 if i is None: 

2331 # first time in - set to last entry 

2332 i = len(h) - 1 

2333 elif i > 0: 

2334 i -= 1 

2335 commandName = h[i] 

2336 k.commandIndex = i 

2337 k.setLabel(k.mb_prefix + commandName) 

2338 #@+node:ekr.20150425143043.1: *4* k.initCommandHistory 

2339 def initCommandHistory(self): 

2340 """Init command history from @data command-history nodes.""" 

2341 k, c = self, self.c 

2342 aList = c.config.getData('history-list') or [] 

2343 for command in reversed(aList): 

2344 k.addToCommandHistory(command) 

2345 

2346 def resetCommandHistory(self): 

2347 """ reset the command history index to indicate that 

2348 we are pointing 'past' the last entry 

2349 """ 

2350 self.commandIndex = None 

2351 # 

2352 #@+node:ekr.20150402111935.1: *4* k.sortCommandHistory 

2353 def sortCommandHistory(self): 

2354 """Sort the command history.""" 

2355 k = self 

2356 k.commandHistory.sort() 

2357 k.commandIndex = None 

2358 #@+node:ekr.20061031131434.104: *3* k.Dispatching 

2359 #@+node:ekr.20061031131434.111: *4* k.fullCommand (alt-x) & helper 

2360 @cmd('full-command') 

2361 def fullCommand( 

2362 self, 

2363 event, 

2364 specialStroke=None, 

2365 specialFunc=None, 

2366 help=False, 

2367 helpHandler=None, 

2368 ): 

2369 """Handle 'full-command' (alt-x) mode.""" 

2370 try: 

2371 c, k = self.c, self 

2372 state = k.getState('full-command') 

2373 helpPrompt = 'Help for command: ' 

2374 c.check_event(event) 

2375 ch = char = event.char if event else '' 

2376 if state == 0: 

2377 k.mb_event = event # Save the full event for later. 

2378 k.setState('full-command', 1, handler=k.fullCommand) 

2379 prompt = helpPrompt if help else k.altX_prompt 

2380 k.setLabelBlue(prompt) 

2381 k.mb_help = help 

2382 k.mb_helpHandler = helpHandler 

2383 c.minibufferWantsFocus() 

2384 elif char == 'Ins' or k.isFKey(char): 

2385 pass 

2386 elif char == 'Escape': 

2387 k.keyboardQuit() 

2388 elif char == 'Down': 

2389 k.commandHistoryFwd() 

2390 elif char == 'Up': 

2391 k.commandHistoryBackwd() 

2392 elif char in ('\n', 'Return'): 

2393 # Fix bug 157: save and restore the selection. 

2394 w = k.mb_event and k.mb_event.w 

2395 if w and hasattr(w, 'hasSelection') and w.hasSelection(): 

2396 sel1, sel2 = w.getSelectionRange() 

2397 ins = w.getInsertPoint() 

2398 c.frame.log.deleteTab('Completion') 

2399 w.setSelectionRange(sel1, sel2, insert=ins) 

2400 else: 

2401 c.frame.log.deleteTab('Completion') 

2402 # 2016/04/27 

2403 if k.mb_help: 

2404 s = k.getLabel() 

2405 commandName = s[len(helpPrompt) :].strip() 

2406 k.clearState() 

2407 k.resetLabel() 

2408 if k.mb_helpHandler: 

2409 k.mb_helpHandler(commandName) 

2410 else: 

2411 s = k.getLabel(ignorePrompt=True) 

2412 commandName = s.strip() 

2413 ok = k.callAltXFunction(k.mb_event) 

2414 if ok: 

2415 k.addToCommandHistory(commandName) 

2416 elif char in ('\t', 'Tab'): 

2417 k.doTabCompletion(list(c.commandsDict.keys())) 

2418 c.minibufferWantsFocus() 

2419 elif char in ('\b', 'BackSpace'): 

2420 k.doBackSpace(list(c.commandsDict.keys())) 

2421 c.minibufferWantsFocus() 

2422 elif k.ignore_unbound_non_ascii_keys and len(ch) > 1: 

2423 if specialStroke: 

2424 g.trace(specialStroke) 

2425 specialFunc() 

2426 c.minibufferWantsFocus() 

2427 else: 

2428 # Clear the list, any other character besides tab indicates that a new prefix is in effect. 

2429 k.mb_tabList = [] 

2430 k.updateLabel(event) 

2431 k.mb_tabListPrefix = k.getLabel() 

2432 c.minibufferWantsFocus() 

2433 except Exception: 

2434 g.es_exception() 

2435 self.keyboardQuit() 

2436 #@+node:ekr.20061031131434.112: *5* k.callAltXFunction 

2437 def callAltXFunction(self, event): 

2438 """Call the function whose name is in the minibuffer.""" 

2439 c, k = self.c, self 

2440 k.mb_tabList = [] 

2441 commandName, tail = k.getMinibufferCommandName() 

2442 k.functionTail = tail 

2443 if commandName and commandName.isdigit(): 

2444 # The line number Easter Egg. 

2445 

2446 def func(event=None): 

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

2448 

2449 else: 

2450 func = c.commandsDict.get(commandName) 

2451 if func: 

2452 # These must be done *after* getting the command. 

2453 k.clearState() 

2454 k.resetLabel() 

2455 if commandName != 'repeat-complex-command': 

2456 k.mb_history.insert(0, commandName) 

2457 w = event and event.widget 

2458 if hasattr(w, 'permanent') and not w.permanent: 

2459 # In a headline that is being edited. 

2460 c.endEditing() 

2461 c.bodyWantsFocusNow() 

2462 # Change the event widget so we don't refer to the to-be-deleted headline widget. 

2463 event.w = event.widget = c.frame.body.wrapper.widget 

2464 else: 

2465 c.widgetWantsFocusNow(event and event.widget) # So cut-text works, e.g. 

2466 try: 

2467 func(event) 

2468 except Exception: 

2469 g.es_exception() 

2470 return True 

2471 # Show possible completions if the command does not exist. 

2472 k.doTabCompletion(list(c.commandsDict.keys())) 

2473 return False 

2474 #@+node:ekr.20061031131434.114: *3* k.Externally visible commands 

2475 #@+node:ekr.20070613133500: *4* k.menuCommandKey 

2476 def menuCommandKey(self, event=None): 

2477 # This method must exist, but it never gets called. 

2478 pass 

2479 #@+node:ekr.20061031131434.119: *4* k.printBindings & helper 

2480 @cmd('show-bindings') 

2481 def printBindings(self, event=None): 

2482 """Print all the bindings presently in effect.""" 

2483 c, k = self.c, self 

2484 d = k.bindingsDict 

2485 tabName = 'Bindings' 

2486 c.frame.log.clearTab(tabName) 

2487 legend = '''\ 

2488 legend: 

2489 [ ] leoSettings.leo 

2490 [D] default binding 

2491 [F] loaded .leo File 

2492 [M] myLeoSettings.leo 

2493 [@] @mode, @button, @command 

2494 

2495 ''' 

2496 if not d: 

2497 return g.es('no bindings') 

2498 legend = textwrap.dedent(legend) 

2499 data = [] 

2500 for stroke in sorted(d): 

2501 assert g.isStroke(stroke), stroke 

2502 aList = d.get(stroke, []) 

2503 for bi in aList: 

2504 s1 = '' if bi.pane == 'all' else bi.pane 

2505 s2 = k.prettyPrintKey(stroke) 

2506 s3 = bi.commandName 

2507 s4 = bi.kind or '<no hash>' 

2508 data.append((s1, s2, s3, s4),) 

2509 # Print keys by type. 

2510 result = [] 

2511 result.append('\n' + legend) 

2512 for prefix in ( 

2513 'Alt+Ctrl+Shift', 'Alt+Ctrl', 'Alt+Shift', 'Alt', # 'Alt+Key': done by Alt. 

2514 'Ctrl+Meta+Shift', 'Ctrl+Meta', 'Ctrl+Shift', 'Ctrl', # Ctrl+Key: done by Ctrl. 

2515 'Meta+Key', 'Meta+Shift', 'Meta', 

2516 'Shift', 

2517 'F', # #1972 

2518 # Careful: longer prefixes must come before shorter prefixes. 

2519 ): 

2520 data2 = [] 

2521 for item in data: 

2522 s1, s2, s3, s4 = item 

2523 if s2.startswith(prefix): 

2524 data2.append(item) 

2525 result.append(f"{prefix} keys...\n") 

2526 self.printBindingsHelper(result, data2, prefix=prefix) 

2527 # Remove all the items in data2 from data. 

2528 # This must be done outside the iterator on data. 

2529 for item in data2: 

2530 data.remove(item) 

2531 # Print all plain bindings. 

2532 result.append('Plain keys...\n') 

2533 self.printBindingsHelper(result, data, prefix=None) 

2534 if not g.unitTesting: 

2535 g.es_print('', ''.join(result), tabName=tabName) 

2536 k.showStateAndMode() 

2537 return result # for unit test. 

2538 #@+node:ekr.20061031131434.120: *5* printBindingsHelper 

2539 def printBindingsHelper(self, result, data, prefix): 

2540 """Helper for k.printBindings""" 

2541 c, lm = self.c, g.app.loadManager 

2542 data.sort(key=lambda x: x[1]) 

2543 data2, n = [], 0 

2544 for pane, key, commandName, kind in data: 

2545 key = key.replace('+Key', '') 

2546 letter = lm.computeBindingLetter(c, kind) 

2547 pane = f"{pane if pane else 'all':4}: " 

2548 left = pane + key # pane and shortcut fields 

2549 n = max(n, len(left)) 

2550 data2.append((letter, left, commandName),) 

2551 for z in data2: 

2552 letter, left, commandName = z 

2553 result.append('%s %*s %s\n' % (letter, -n, left, commandName)) 

2554 if data: 

2555 result.append('\n') 

2556 #@+node:ekr.20120520174745.9867: *4* k.printButtons 

2557 @cmd('show-buttons') 

2558 def printButtons(self, event=None): 

2559 """Print all @button and @command commands, their bindings and their source.""" 

2560 c = self.c 

2561 tabName = '@buttons && @commands' 

2562 c.frame.log.clearTab(tabName) 

2563 

2564 def put(s): 

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

2566 

2567 data = [] 

2568 for aList in [c.config.getButtons(), c.config.getCommands()]: 

2569 for z in aList: 

2570 p, script = z 

2571 c = p.v.context 

2572 tag = 'M' if c.shortFileName().endswith('myLeoSettings.leo') else 'G' 

2573 data.append((p.h, tag),) 

2574 for aList in [g.app.config.atLocalButtonsList, g.app.config.atLocalCommandsList]: 

2575 for p in aList: 

2576 data.append((p.h, 'L'),) 

2577 result = [f"{z[1]} {z[0]}" for z in sorted(data)] 

2578 result.extend([ 

2579 '', 

2580 'legend:', 

2581 'G leoSettings.leo', 

2582 'L local .leo File', 

2583 'M myLeoSettings.leo', 

2584 ]) 

2585 put('\n'.join(result)) 

2586 #@+node:ekr.20061031131434.121: *4* k.printCommands 

2587 @cmd('show-commands') 

2588 def printCommands(self, event=None): 

2589 """Print all the known commands and their bindings, if any.""" 

2590 c, k = self.c, self 

2591 tabName = 'Commands' 

2592 c.frame.log.clearTab(tabName) 

2593 inverseBindingDict = k.computeInverseBindingDict() 

2594 data, n = [], 0 

2595 for commandName in sorted(c.commandsDict): 

2596 dataList = inverseBindingDict.get(commandName, [('', ''),]) 

2597 for z in dataList: 

2598 pane, key = z 

2599 pane = f"{pane} " if pane != 'all:' else '' 

2600 key = k.prettyPrintKey(key).replace('+Key', '') 

2601 s1 = pane + key 

2602 s2 = commandName 

2603 n = max(n, len(s1)) 

2604 data.append((s1, s2),) 

2605 # This isn't perfect in variable-width fonts. 

2606 lines = ['%*s %s\n' % (-n, z1, z2) for z1, z2 in data] 

2607 g.es_print('', ''.join(lines), tabName=tabName) 

2608 #@+node:tom.20220320235059.1: *4* k.printCommandsWithDocs 

2609 @cmd('show-commands-with-docs') 

2610 def printCommandsWithDocs(self, event=None): 

2611 """Show all the known commands and their bindings, if any.""" 

2612 c, k = self.c, self 

2613 tabName = 'List' 

2614 c.frame.log.clearTab(tabName) 

2615 inverseBindingDict = k.computeInverseBindingDict() 

2616 data = [] 

2617 for commandName in sorted(c.commandsDict): 

2618 dataList = inverseBindingDict.get(commandName, [('', ''),]) 

2619 for pane, key in dataList: 

2620 key = k.prettyPrintKey(key) 

2621 binding = pane + key 

2622 cmd = commandName.strip() 

2623 doc = f'{c.commandsDict.get(commandName).__doc__}' or '' 

2624 if doc == 'None': 

2625 doc = '' 

2626 # Formatting for multi-line docstring 

2627 if doc.count('\n') > 0: 

2628 doc = f'\n{doc}\n' 

2629 else: 

2630 doc = f' {doc}' 

2631 if doc.startswith('\n'): 

2632 doc.replace('\n', '', 1) 

2633 toolong = doc.count('\n') > 5 

2634 manylines = False 

2635 if toolong: 

2636 lines = doc.split('\n')[:4] 

2637 lines[-1] += ' ...\n' 

2638 doc = '\n'.join(lines) 

2639 manylines = True 

2640 n = min(2, len(binding)) 

2641 if manylines: 

2642 doc = textwrap.fill(doc, width = 50, initial_indent = ' '*4, 

2643 subsequent_indent = ' '*4) 

2644 data.append((binding, cmd, doc)) 

2645 lines = ['[%*s] %s%s\n' % (-n, binding, cmd, doc) for binding, cmd, doc in data] 

2646 g.es(''.join(lines), tabName = tabName) 

2647 #@+node:ekr.20061031131434.122: *4* k.repeatComplexCommand 

2648 @cmd('repeat-complex-command') 

2649 def repeatComplexCommand(self, event): 

2650 """Repeat the previously executed minibuffer command.""" 

2651 k = self 

2652 # #2286: Always call k.fullCommand. 

2653 k.setState('getArg', 0, handler=k.fullCommand) 

2654 k.fullCommand(event) # #2334 

2655 if not k.mb_history: 

2656 k.mb_history = list(reversed(k.commandHistory)) 

2657 command = k.mb_history[0] if k.mb_history else '' 

2658 k.setLabelBlue(f"{k.altX_prompt}", protect=True) 

2659 k.extendLabel(command, select=False, protect=False) 

2660 #@+node:ekr.20061031131434.123: *4* k.set-xxx-State 

2661 @cmd('set-command-state') 

2662 def setCommandState(self, event): 

2663 """Enter the 'command' editing state.""" 

2664 k = self 

2665 k.setInputState('command', set_border=True) 

2666 # This command is also valid in headlines. 

2667 # k.c.bodyWantsFocus() 

2668 k.showStateAndMode() 

2669 

2670 @cmd('set-insert-state') 

2671 def setInsertState(self, event): 

2672 """Enter the 'insert' editing state.""" 

2673 k = self 

2674 k.setInputState('insert', set_border=True) 

2675 # This command is also valid in headlines. 

2676 # k.c.bodyWantsFocus() 

2677 k.showStateAndMode() 

2678 

2679 @cmd('set-overwrite-state') 

2680 def setOverwriteState(self, event): 

2681 """Enter the 'overwrite' editing state.""" 

2682 k = self 

2683 k.setInputState('overwrite', set_border=True) 

2684 # This command is also valid in headlines. 

2685 # k.c.bodyWantsFocus() 

2686 k.showStateAndMode() 

2687 #@+node:ekr.20061031131434.124: *4* k.toggle-input-state 

2688 @cmd('toggle-input-state') 

2689 def toggleInputState(self, event=None): 

2690 """The toggle-input-state command.""" 

2691 c, k = self.c, self 

2692 default = c.config.getString('top-level-unbound-key-action') or 'insert' 

2693 state = k.unboundKeyAction 

2694 if default == 'insert': 

2695 state = 'command' if state == 'insert' else 'insert' 

2696 elif default == 'overwrite': 

2697 state = 'command' if state == 'overwrite' else 'overwrite' 

2698 else: 

2699 state = 'insert' if state == 'command' else 'command' # prefer insert to overwrite. 

2700 k.setInputState(state) 

2701 k.showStateAndMode() 

2702 #@+node:ekr.20061031131434.125: *3* k.Externally visible helpers 

2703 #@+node:ekr.20140816165728.18968: *4* Wrappers for GetArg methods 

2704 # New in Leo 5.4 

2705 

2706 def getNextArg(self, handler): 

2707 """ 

2708 Get the next arg. For example, after a Tab in the find commands. 

2709 See the docstring for k.get1Arg for examples of its use. 

2710 """ 

2711 # Replace the current handler. 

2712 self.getArgInstance.after_get_arg_state = ('getarg', 1, handler) 

2713 self.c.minibufferWantsFocusNow() 

2714 

2715 # New in Leo 5.4 

2716 

2717 def get1Arg(self, event, handler, 

2718 prefix=None, tabList=None, completion=True, oneCharacter=False, 

2719 stroke=None, useMinibuffer=True 

2720 ): 

2721 #@+<< docstring for k.get1arg >> 

2722 #@+node:ekr.20161020031633.1: *5* << docstring for k.get1arg >> 

2723 """ 

2724 k.get1Arg: Handle the next character the user types when accumulating 

2725 a user argument from the minibuffer. Ctrl-G will abort this processing 

2726 at any time. 

2727 

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

2729 k.getNextArg to get all other arguments. 

2730 

2731 Before going into the many details, let's look at some examples. This 

2732 code will work in any class having a 'c' ivar bound to a commander. 

2733 

2734 Example 1: get one argument from the user: 

2735 

2736 @g.command('my-command') 

2737 def myCommand(self, event): 

2738 k = self.c.k 

2739 k.setLabelBlue('prompt: ') 

2740 k.get1Arg(event, handler=self.myCommand1) 

2741 

2742 def myCommand1(self, event): 

2743 k = self.c.k 

2744 # k.arg contains the argument. 

2745 # Finish the command. 

2746 ... 

2747 # Reset the minibuffer. 

2748 k.clearState() 

2749 k.resetLabel() 

2750 k.showStateAndMode() 

2751 

2752 Example 2: get two arguments from the user: 

2753 

2754 @g.command('my-command') 

2755 def myCommand(self, event): 

2756 k = self.c.k 

2757 k.setLabelBlue('first prompt: ') 

2758 k.get1Arg(event, handler=self.myCommand1) 

2759 

2760 def myCommand1(self, event): 

2761 k = self.c.k 

2762 self.arg1 = k.arg 

2763 k.extendLabel(' second prompt: ', select=False, protect=True) 

2764 k.getNextArg(handler=self.myCommand2) 

2765 

2766 def myCommand2(self, event): 

2767 k = self.c.k 

2768 # k.arg contains second argument. 

2769 # Finish the command, using self.arg1 and k.arg. 

2770 ... 

2771 # Reset the minibuffer. 

2772 k.clearState() 

2773 k.resetLabel() 

2774 k.showStateAndMode() 

2775 

2776 k.get1Arg and k.getNextArg are a convenience methods. They simply pass 

2777 their arguments to the get_arg method of the singleton GetArg 

2778 instance. This docstring describes k.get1arg and k.getNextArg as if 

2779 they were the corresponding methods of the GetArg class. 

2780 

2781 k.get1Arg is a state machine. Logically, states are tuples (kind, n, 

2782 handler) though they aren't represented that way. When the state 

2783 machine in the GetArg class is active, the kind is 'getArg'. This 

2784 constant has special meaning to Leo's key-handling code. 

2785 

2786 The arguments to k.get1Arg are as follows: 

2787 

2788 event: The event passed to the command. 

2789 

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

2791 when the user completes the argument by typing 

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

2793 

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

2795 

2796 completion=True: True if completions are enabled. 

2797 

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

2799 

2800 stroke=None: The incoming key stroke. 

2801 

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

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

2804 

2805 """ 

2806 #@-<< docstring for k.get1arg >> 

2807 returnKind, returnState = None, None 

2808 assert handler, g.callers() 

2809 self.getArgInstance.get_arg(event, returnKind, returnState, handler, 

2810 tabList, completion, oneCharacter, stroke, useMinibuffer) 

2811 

2812 def getArg(self, event, 

2813 returnKind=None, returnState=None, handler=None, 

2814 prefix=None, tabList=None, completion=True, oneCharacter=False, 

2815 stroke=None, useMinibuffer=True 

2816 ): 

2817 """Convenience method mapping k.getArg to ga.get_arg.""" 

2818 self.getArgInstance.get_arg(event, returnKind, returnState, handler, 

2819 tabList, completion, oneCharacter, stroke, useMinibuffer) 

2820 

2821 def doBackSpace(self, tabList, completion=True): 

2822 """Convenience method mapping k.doBackSpace to ga.do_back_space.""" 

2823 self.getArgInstance.do_back_space(tabList, completion) 

2824 

2825 def doTabCompletion(self, tabList): 

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

2827 self.getArgInstance.do_tab(tabList) 

2828 

2829 def getMinibufferCommandName(self): 

2830 """ 

2831 Convenience method mapping k.getMinibufferCommandName to 

2832 ga.get_minibuffer_command_name. 

2833 """ 

2834 return self.getArgInstance.get_minibuffer_command_name() 

2835 #@+node:ekr.20061031131434.130: *4* k.keyboardQuit 

2836 @cmd('keyboard-quit') 

2837 def keyboardQuit(self, event=None, setFocus=True): 

2838 """Clears the state and the minibuffer label.""" 

2839 c, k = self.c, self 

2840 if g.app.quitting: 

2841 return 

2842 c.endEditing() 

2843 # Completely clear the mode. 

2844 if setFocus: 

2845 c.frame.log.deleteTab('Mode') 

2846 c.frame.log.hideTab('Completion') 

2847 if k.inputModeName: 

2848 k.endMode() 

2849 # Complete clear the state. 

2850 k.state.kind = None 

2851 k.state.n = None 

2852 k.clearState() 

2853 k.resetLabel() 

2854 if setFocus: 

2855 c.bodyWantsFocus() 

2856 # At present, only the auto-completer suppresses this. 

2857 k.setDefaultInputState() 

2858 if c.vim_mode and c.vimCommands: 

2859 c.vimCommands.reset(setFocus=setFocus) 

2860 else: 

2861 # This was what caused the unwanted scrolling. 

2862 k.showStateAndMode(setFocus=setFocus) 

2863 k.resetCommandHistory() 

2864 #@+node:ekr.20061031131434.126: *4* k.manufactureKeyPressForCommandName (only for unit tests!) 

2865 def manufactureKeyPressForCommandName(self, w, commandName): 

2866 """ 

2867 Implement a command by passing a keypress to the gui. 

2868 

2869 **Only unit tests use this method.** 

2870 """ 

2871 c, k = self.c, self 

2872 # Unit tests do not ordinarily read settings files. 

2873 stroke = k.getStrokeForCommandName(commandName) 

2874 if stroke is None: 

2875 # Create the stroke and binding info data. 

2876 stroke = g.KeyStroke('Ctrl+F1') 

2877 bi = g.BindingInfo( 

2878 kind='manufactured-binding', 

2879 commandName=commandName, 

2880 func=None, 

2881 pane='all', 

2882 stroke=stroke, 

2883 ) 

2884 # Make the binding! 

2885 # k.masterBindingsDict keys: scope names; values: masterBindingDicts (3) 

2886 # Interior masterBindingDicts: Keys are strokes; values are BindingInfo objects. 

2887 d = k.masterBindingsDict 

2888 d2 = d.get('all', {}) 

2889 d2[stroke] = bi 

2890 d['all'] = d2 

2891 assert g.isStroke(stroke), (commandName, stroke.__class__.__name__) 

2892 shortcut = stroke.s 

2893 shortcut = g.checkUnicode(shortcut) 

2894 if shortcut and w: 

2895 g.app.gui.set_focus(c, w) 

2896 g.app.gui.event_generate(c, None, shortcut, w) 

2897 else: 

2898 message = f"no shortcut for {commandName}" 

2899 if g.unitTesting: 

2900 raise AttributeError(message) 

2901 g.error(message) 

2902 #@+node:ekr.20071212104050: *4* k.overrideCommand 

2903 def overrideCommand(self, commandName, func): 

2904 # Override entries in c.k.masterBindingsDict 

2905 k = self 

2906 d = k.masterBindingsDict 

2907 for key in d: 

2908 d2 = d.get(key) 

2909 for key2 in d2: 

2910 bi = d2.get(key2) 

2911 if bi.commandName == commandName: 

2912 bi.func = func 

2913 d2[key2] = bi 

2914 #@+node:ekr.20061031131434.131: *4* k.registerCommand 

2915 def registerCommand(self, commandName, func, 

2916 allowBinding=False, 

2917 pane='all', 

2918 shortcut=None, # Must be None unless allowBindings is True. 

2919 ** kwargs 

2920 ): 

2921 """ 

2922 Make the function available as a minibuffer command. 

2923 

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

2925 restriction to functions is not significant. 

2926 

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

2928 

2929 Only k.bindOpenWith and the mod_scripting.py plugin should set 

2930 allowBinding. 

2931 """ 

2932 c, k = self.c, self 

2933 if not func: 

2934 g.es_print('Null func passed to k.registerCommand\n', commandName) 

2935 return 

2936 f = c.commandsDict.get(commandName) 

2937 if f and f.__name__ != func.__name__: 

2938 g.trace('redefining', commandName, f, '->', func) 

2939 c.commandsDict[commandName] = func 

2940 # Warn about deprecated arguments. 

2941 if shortcut and not allowBinding: 

2942 g.es_print('The "shortcut" keyword arg to k.registerCommand will be ignored') 

2943 g.es_print('Called from', g.callers()) 

2944 shortcut = None 

2945 for arg, val in kwargs.items(): 

2946 if val is not None: 

2947 g.es_print(f'The "{arg}" keyword arg to k.registerCommand is deprecated') 

2948 g.es_print('Called from', g.callers()) 

2949 # Make requested bindings, even if a warning has been given. 

2950 # This maintains strict compatibility with existing plugins and scripts. 

2951 k.registerCommandShortcut( 

2952 commandName=commandName, 

2953 func=func, 

2954 pane=pane, 

2955 shortcut=shortcut, 

2956 ) 

2957 #@+node:ekr.20171124043747.1: *4* k.registerCommandShortcut 

2958 def registerCommandShortcut(self, commandName, func, pane, shortcut): 

2959 """ 

2960 Register a shortcut for the a command. 

2961 

2962 **Important**: Bindings created here from plugins can not be overridden. 

2963 This includes @command and @button bindings created by mod_scripting.py. 

2964 """ 

2965 c, k = self.c, self 

2966 is_local = c.shortFileName() not in ('myLeoSettings.leo', 'leoSettings.leo') 

2967 assert not g.isStroke(shortcut) 

2968 if shortcut: 

2969 stroke = g.KeyStroke(binding=shortcut) if shortcut else None 

2970 elif commandName.lower() == 'shortcut': # Causes problems. 

2971 stroke = None 

2972 elif is_local: 

2973 # 327: Don't get defaults when handling a local file. 

2974 stroke = None 

2975 else: 

2976 # Try to get a stroke from leoSettings.leo. 

2977 stroke = None 

2978 junk, aList = c.config.getShortcut(commandName) 

2979 for bi in aList: 

2980 if bi.stroke and not bi.pane.endswith('-mode'): 

2981 stroke = bi.stroke 

2982 pane = bi.pane # 2015/05/11. 

2983 break 

2984 if stroke: 

2985 k.bindKey(pane, stroke, func, commandName, tag='register-command') # Must be a stroke. 

2986 k.makeMasterGuiBinding(stroke) # Must be a stroke. 

2987 # Fixup any previous abbreviation to press-x-button commands. 

2988 if commandName.startswith('press-') and commandName.endswith('-button'): 

2989 d = c.config.getAbbrevDict() # Keys are full command names, values are abbreviations. 

2990 if commandName in list(d.values()): 

2991 for key in d: 

2992 if d.get(key) == commandName: 

2993 c.commandsDict[key] = c.commandsDict.get(commandName) 

2994 break 

2995 #@+node:ekr.20061031131434.127: *4* k.simulateCommand 

2996 def simulateCommand(self, commandName, event=None): 

2997 """Execute a Leo command by name.""" 

2998 c = self.c 

2999 if not event: 

3000 # Create a default key event. 

3001 event = g.app.gui.create_key_event(c) 

3002 c.doCommandByName(commandName, event) 

3003 #@+node:ekr.20140813052702.18203: *4* k.getFileName 

3004 def getFileName(self, event, callback=None, 

3005 filterExt=None, prompt='Enter File Name: ', tabName='Dired' 

3006 ): 

3007 """Get a file name from the minibuffer.""" 

3008 k = self 

3009 k.fnc.get_file_name(event, callback, filterExt, prompt, tabName) 

3010 #@+node:ekr.20061031131434.145: *3* k.Master event handlers 

3011 #@+node:ekr.20061031131434.146: *4* k.masterKeyHandler & helpers 

3012 def masterKeyHandler(self, event): 

3013 """The master key handler for almost all key bindings.""" 

3014 trace = 'keys' in g.app.debug 

3015 c, k = self.c, self 

3016 # Setup... 

3017 if trace: 

3018 g.trace(repr(k.state.kind), repr(event.char), repr(event.stroke)) 

3019 k.checkKeyEvent(event) 

3020 k.setEventWidget(event) 

3021 k.traceVars(event) 

3022 # Order is very important here... 

3023 if k.isSpecialKey(event): 

3024 return 

3025 if k.doKeyboardQuit(event): 

3026 return 

3027 if k.doDemo(event): 

3028 return 

3029 if k.doMode(event): 

3030 return 

3031 if k.doVim(event): 

3032 return 

3033 if k.doBinding(event): 

3034 return 

3035 # Handle abbreviations. 

3036 if k.abbrevOn and c.abbrevCommands.expandAbbrev(event, event.stroke): 

3037 return 

3038 # Handle the character given by event *without* 

3039 # executing any command that might be bound to it. 

3040 c.insertCharFromEvent(event) 

3041 #@+node:ekr.20200524151214.1: *5* Setup... 

3042 #@+node:ekr.20180418040158.1: *6* k.checkKeyEvent 

3043 def checkKeyEvent(self, event): 

3044 """Perform sanity checks on the incoming event.""" 

3045 # These assert's should be safe, because eventFilter 

3046 # calls k.masterKeyHandler inside a try/except block. 

3047 c = self.c 

3048 assert event is not None 

3049 c.check_event(event) 

3050 assert hasattr(event, 'char') 

3051 assert hasattr(event, 'stroke') 

3052 if not hasattr(event, 'widget'): 

3053 event.widget = None 

3054 assert g.isStrokeOrNone(event.stroke) 

3055 if event: 

3056 assert event.stroke.s not in g.app.gui.ignoreChars, repr(event.stroke.s) 

3057 # A continuous unit test, better than "@test k.isPlainKey". 

3058 #@+node:ekr.20180418034305.1: *6* k.setEventWidget 

3059 def setEventWidget(self, event): 

3060 """ 

3061 A hack: redirect the event to the text part of the log. 

3062 """ 

3063 c = self.c 

3064 w = event.widget 

3065 w_name = c.widget_name(w) 

3066 if w_name.startswith('log'): 

3067 event.widget = c.frame.log.logCtrl 

3068 #@+node:ekr.20180418031417.1: *6* k.traceVars 

3069 def traceVars(self, event): 

3070 

3071 trace = False and not g.unitTesting 

3072 if not trace: 

3073 return 

3074 k = self 

3075 char = event.char 

3076 state = k.state.kind 

3077 stroke = event.stroke 

3078 g.trace( 

3079 f"stroke: {stroke!r}, " 

3080 f"char: {char!r}, " 

3081 f"state: {state}, " 

3082 f"state2: {k.unboundKeyAction}") 

3083 #@+node:ekr.20180418031118.1: *5* 1. k.isSpecialKey 

3084 def isSpecialKey(self, event): 

3085 """Return True if char is a special key.""" 

3086 if not event: 

3087 # An empty event is not an error. 

3088 return False 

3089 # Fix #917. 

3090 if len(event.char) > 1 and not event.stroke.s: 

3091 # stroke.s was cleared, but not event.char. 

3092 return True 

3093 return event.char in g.app.gui.ignoreChars 

3094 #@+node:ekr.20180418024449.1: *5* 2. k.doKeyboardQuit 

3095 def doKeyboardQuit(self, event): 

3096 """ 

3097 A helper for k.masterKeyHandler: Handle keyboard-quit logic. 

3098 

3099 return True if k.masterKeyHandler should return. 

3100 """ 

3101 c, k = self.c, self 

3102 stroke = getattr(event, 'stroke', None) 

3103 if k.abortAllModesKey and stroke and stroke == k.abortAllModesKey: 

3104 if getattr(c, 'screenCastController', None): 

3105 c.screenCastController.quit() 

3106 c.doCommandByName('keyboard-quit', event) 

3107 return True 

3108 return False 

3109 #@+node:ekr.20180418023827.1: *5* 3. k.doDemo 

3110 def doDemo(self, event): 

3111 """ 

3112 Support the demo.py plugin. 

3113 Return True if k.masterKeyHandler should return. 

3114 """ 

3115 k = self 

3116 stroke = event.stroke 

3117 demo = getattr(g.app, 'demo', None) 

3118 if not demo: 

3119 return False 

3120 # 

3121 # Shortcut everything so that demo-next or demo-prev won't alter of our ivars. 

3122 if k.demoNextKey and stroke == k.demoNextKey: 

3123 if demo.trace: 

3124 g.trace('demo-next', stroke) 

3125 demo.next_command() 

3126 return True 

3127 if k.demoPrevKey and stroke == k.demoPrevKey: 

3128 if demo.trace: 

3129 g.trace('demo-prev', stroke) 

3130 demo.prev_command() 

3131 return True 

3132 return False 

3133 #@+node:ekr.20091230094319.6244: *5* 4. k.doMode & helpers 

3134 def doMode(self, event): 

3135 """ 

3136 Handle mode bindings. 

3137 Return True if k.masterKeyHandler should return. 

3138 """ 

3139 # #1757: Leo's default vim bindings make heavy use of modes. 

3140 # Retain these traces! 

3141 trace = 'keys' in g.app.debug 

3142 k = self 

3143 state = k.state.kind 

3144 stroke = event.stroke 

3145 if not k.inState(): 

3146 return False 

3147 # First, honor minibuffer bindings for all except user modes. 

3148 if state == 'input-shortcut': 

3149 k.handleInputShortcut(event, stroke) 

3150 if trace: 

3151 g.trace(state, 'k.handleInputShortcut', stroke) 

3152 return True 

3153 if state in ( 

3154 'getArg', 'getFileName', 'full-command', 'auto-complete', 'vim-mode' 

3155 ): 

3156 if k.handleMiniBindings(event, state, stroke): 

3157 if trace: 

3158 g.trace(state, 'k.handleMiniBindings', stroke) 

3159 return True 

3160 # Second, honor general modes. 

3161 if state == 'getArg': 

3162 # New in Leo 5.8: Only call k.getArg for keys it can handle. 

3163 if k.isPlainKey(stroke): 

3164 k.getArg(event, stroke=stroke) 

3165 if trace: 

3166 g.trace(state, 'k.isPlain: getArg', stroke) 

3167 return True 

3168 if stroke.s in ('Escape', 'Tab', 'BackSpace'): 

3169 k.getArg(event, stroke=stroke) 

3170 if trace: 

3171 g.trace(state, f"{stroke.s!r}: getArg", stroke) 

3172 return True 

3173 return False 

3174 if state in ('getFileName', 'get-file-name'): 

3175 k.getFileName(event) 

3176 if trace: 

3177 g.trace(state, 'k.getFileName', stroke) 

3178 return True 

3179 if state in ('full-command', 'auto-complete'): 

3180 # Do the default state action. Calls end-command. 

3181 val = k.callStateFunction(event) 

3182 if val != 'do-standard-keys': 

3183 handler = k.state.handler and k.state.handler.__name__ or '<no handler>' 

3184 if trace: 

3185 g.trace(state, 'k.callStateFunction:', handler, stroke) 

3186 return True 

3187 return False 

3188 # Third, pass keys to user modes. 

3189 d = k.masterBindingsDict.get(state) 

3190 if d: 

3191 assert g.isStrokeOrNone(stroke) 

3192 bi = d.get(stroke) 

3193 if bi: 

3194 # Bound keys continue the mode. 

3195 k.generalModeHandler(event, 

3196 commandName=bi.commandName, 

3197 func=bi.func, 

3198 modeName=state, 

3199 nextMode=bi.nextMode) 

3200 if trace: 

3201 g.trace(state, 'k.generalModeHandler', stroke) 

3202 return True 

3203 # Unbound keys end mode. 

3204 k.endMode() 

3205 return False 

3206 # Fourth, call the state handler. 

3207 handler = k.getStateHandler() 

3208 if handler: 

3209 handler(event) 

3210 if trace: 

3211 handler_name = handler and handler.__name__ or '<no handler>' 

3212 g.trace(state, 'handler:', handler_name, stroke) 

3213 return True 

3214 #@+node:ekr.20061031131434.108: *6* k.callStateFunction 

3215 def callStateFunction(self, event): 

3216 """Call the state handler associated with this event.""" 

3217 k = self 

3218 ch = event.char 

3219 # 

3220 # Defensive programming 

3221 if not k.state.kind: 

3222 return None 

3223 if not k.state.handler: 

3224 g.error('callStateFunction: no state function for', k.state.kind) 

3225 return None 

3226 # 

3227 # Handle auto-completion before checking for unbound keys. 

3228 if k.state.kind == 'auto-complete': 

3229 # k.auto_completer_state_handler returns 'do-standard-keys' for control keys. 

3230 val = k.state.handler(event) 

3231 return val 

3232 # 

3233 # Ignore unbound non-ascii keys. 

3234 if ( 

3235 k.ignore_unbound_non_ascii_keys and 

3236 len(ch) == 1 and 

3237 ch and ch not in ('\b', '\n', '\r', '\t') and 

3238 (ord(ch) < 32 or ord(ch) > 128) 

3239 ): 

3240 return None 

3241 # 

3242 # Call the state handler. 

3243 val = k.state.handler(event) 

3244 return val 

3245 #@+node:ekr.20061031131434.152: *6* k.handleMiniBindings 

3246 def handleMiniBindings(self, event, state, stroke): 

3247 """Find and execute commands bound to the event.""" 

3248 k = self 

3249 # 

3250 # Special case for bindings handled in k.getArg: 

3251 if state == 'full-command' and stroke in ('Up', 'Down'): 

3252 return False 

3253 # 

3254 # Ignore other special keys in the minibuffer. 

3255 if state in ('getArg', 'full-command'): 

3256 if stroke in ( 

3257 '\b', 'BackSpace', 

3258 '\r', 'Linefeed', 

3259 '\n', 'Return', 

3260 '\t', 'Tab', 

3261 'Escape', 

3262 ): 

3263 return False 

3264 if k.isFKey(stroke): 

3265 return False 

3266 # 

3267 # Ignore autocompletion state. 

3268 if state.startswith('auto-'): 

3269 return False 

3270 # 

3271 # Ignore plain key binding in the minibuffer. 

3272 if not stroke or k.isPlainKey(stroke): 

3273 return False 

3274 # 

3275 # Get the command, based on the pane. 

3276 for pane in ('mini', 'all', 'text'): 

3277 result = k.handleMinibufferHelper(event, pane, state, stroke) 

3278 assert result in ('continue', 'found', 'ignore') 

3279 if result == 'ignore': 

3280 return False # Let getArg handle it. 

3281 if result == 'found': 

3282 # Do not call k.keyboardQuit here! 

3283 return True 

3284 # 

3285 # No binding exists. 

3286 return False 

3287 #@+node:ekr.20180418114300.1: *7* k.handleMinibufferHelper 

3288 def handleMinibufferHelper(self, event, pane, state, stroke): 

3289 """ 

3290 Execute a pane binding in the minibuffer. 

3291 Return 'continue', 'ignore', 'found' 

3292 """ 

3293 c, k = self.c, self 

3294 d = k.masterBindingsDict.get(pane) 

3295 if not d: 

3296 return 'continue' 

3297 bi = d.get(stroke) 

3298 if not bi: 

3299 return 'continue' 

3300 assert bi.stroke == stroke, f"bi: {bi} stroke: {stroke}" 

3301 # Ignore the replace-string command in the minibuffer. 

3302 if bi.commandName == 'replace-string' and state == 'getArg': 

3303 return 'ignore' 

3304 # Execute this command. 

3305 if bi.commandName not in k.singleLineCommandList: 

3306 k.keyboardQuit() 

3307 else: 

3308 c.minibufferWantsFocus() 

3309 c.doCommandByName(bi.commandName, event) 

3310 # Careful: the command could exit. 

3311 if c.exists and not k.silentMode: 

3312 # Use the state *after* executing the command. 

3313 if k.state.kind: 

3314 c.minibufferWantsFocus() 

3315 else: 

3316 c.bodyWantsFocus() 

3317 return 'found' 

3318 #@+node:vitalije.20170708161511.1: *6* k.handleInputShortcut 

3319 def handleInputShortcut(self, event, stroke): 

3320 c, k, p, u = self.c, self, self.c.p, self.c.undoer 

3321 k.clearState() 

3322 if p.h.startswith(('@shortcuts', '@mode')): 

3323 # line of text in body 

3324 w = c.frame.body.wrapper 

3325 before, sel, after = w.getInsertLines() 

3326 m = k._cmd_handle_input_pattern.search(sel) 

3327 assert m # edit-shortcut was invoked on a malformed body line 

3328 sel = f"{m.group(0)} {stroke.s}" 

3329 udata = u.beforeChangeNodeContents(p) 

3330 pos = w.getYScrollPosition() 

3331 i = len(before) 

3332 j = max(i, len(before) + len(sel) - 1) 

3333 w.setAllText(before + sel + after) 

3334 w.setSelectionRange(i, j, insert=j) 

3335 w.setYScrollPosition(pos) 

3336 u.afterChangeNodeContents(p, 'change shortcut', udata) 

3337 cmdname = m.group(0).rstrip('= ') 

3338 k.editShortcut_do_bind_helper(stroke, cmdname) 

3339 return 

3340 if p.h.startswith(('@command', '@button')): 

3341 udata = u.beforeChangeNodeContents(p) 

3342 cmd = p.h.split('@key', 1)[0] 

3343 p.h = f"{cmd} @key={stroke.s}" 

3344 u.afterChangeNodeContents(p, 'change shortcut', udata) 

3345 try: 

3346 cmdname = cmd.split(' ', 1)[1].strip() 

3347 k.editShortcut_do_bind_helper(stroke, cmdname) 

3348 except IndexError: 

3349 pass 

3350 return 

3351 # this should never happen 

3352 g.error('not in settings node shortcut') 

3353 #@+node:vitalije.20170709151653.1: *7* k.isInShortcutBodyLine 

3354 _cmd_handle_input_pattern = re.compile(r'[A-Za-z0-9_\-]+\s*=') 

3355 

3356 def isInShortcutBodyLine(self): 

3357 c, k = self.c, self 

3358 p = c.p 

3359 if p.h.startswith(('@shortcuts', '@mode')): 

3360 # line of text in body 

3361 w = c.frame.body 

3362 before, sel, after = w.getInsertLines() 

3363 m = k._cmd_handle_input_pattern.search(sel) 

3364 return bool(m) 

3365 return p.h.startswith(('@command', '@button')) 

3366 #@+node:vitalije.20170709151658.1: *7* k.isEditShortcutSensible 

3367 def isEditShortcutSensible(self): 

3368 c, k = self.c, self 

3369 p = c.p 

3370 return p.h.startswith(('@command', '@button')) or k.isInShortcutBodyLine() 

3371 #@+node:vitalije.20170709202924.1: *7* k.editShortcut_do_bind_helper 

3372 def editShortcut_do_bind_helper(self, stroke, cmdname): 

3373 c, k = self.c, self 

3374 cmdfunc = c.commandsDict.get(cmdname) 

3375 if cmdfunc: 

3376 k.bindKey('all', stroke, cmdfunc, cmdname) 

3377 g.es('bound', stroke, 'to command', cmdname) 

3378 #@+node:ekr.20180418025241.1: *5* 5. k.doVim 

3379 def doVim(self, event): 

3380 """ 

3381 Handle vim mode. 

3382 Return True if k.masterKeyHandler should return. 

3383 """ 

3384 trace = all(z in g.app.debug for z in ('keys', 'verbose')) 

3385 c = self.c 

3386 if c.vim_mode and c.vimCommands: 

3387 # The "acceptance methods" in leoVim.py return True 

3388 # if vim node has completely handled the key. 

3389 # Otherwise, processing in k.masterKeyHandler continues. 

3390 ok = c.vimCommands.do_key(event) 

3391 if trace: 

3392 g.trace('do_key returns', ok, repr(event and event.stroke)) 

3393 return ok 

3394 return False 

3395 #@+node:ekr.20180418033838.1: *5* 6. k.doBinding & helpers 

3396 def doBinding(self, event): 

3397 """ 

3398 Attempt to find a binding for the event's stroke. 

3399 If found, execute the command and return True 

3400 Otherwise, return False 

3401 """ 

3402 trace = 'keys' in g.app.debug 

3403 c, k = self.c, self 

3404 # 

3405 # Experimental special case: 

3406 # Inserting a '.' always invokes the auto-completer. 

3407 # The auto-completer just inserts a '.' if it isn't enabled. 

3408 stroke = event.stroke 

3409 if ( 

3410 stroke.s == '.' 

3411 and k.isPlainKey(stroke) 

3412 and self.unboundKeyAction in ('insert', 'overwrite') 

3413 ): 

3414 c.doCommandByName('auto-complete', event) 

3415 return True 

3416 # 

3417 # Use getPaneBindings for *all* keys. 

3418 bi = k.getPaneBinding(event) 

3419 # 

3420 # #327: Ignore killed bindings. 

3421 if bi and bi.commandName in k.killedBindings: 

3422 return False 

3423 # 

3424 # Execute the command if the binding exists. 

3425 if bi: 

3426 # A superb trace. !s gives shorter trace. 

3427 if trace: 

3428 g.trace(f"{event.stroke!s} {bi.commandName}") 

3429 c.doCommandByName(bi.commandName, event) 

3430 return True 

3431 # 

3432 # No binding exists. 

3433 return False 

3434 #@+node:ekr.20091230094319.6240: *6* k.getPaneBinding & helper 

3435 def getPaneBinding(self, event): 

3436 c, k, state = self.c, self, self.unboundKeyAction 

3437 stroke, w = event.stroke, event.w 

3438 if not g.assert_is(stroke, g.KeyStroke): 

3439 return None 

3440 # #1757: Always insert plain keys in the body. 

3441 # Valid because mode bindings have already been handled. 

3442 if ( 

3443 k.isPlainKey(stroke) 

3444 and w == c.frame.body.widget 

3445 and state in ('insert', 'overwrite') 

3446 ): 

3447 return None 

3448 for key, name in ( 

3449 # Order here is similar to bindtags order. 

3450 ('command', None), 

3451 ('insert', None), 

3452 ('overwrite', None), 

3453 ('button', None), 

3454 ('body', 'body'), 

3455 ('text', 'head'), # Important: text bindings in head before tree bindings. 

3456 ('tree', 'head'), 

3457 ('tree', 'canvas'), 

3458 ('log', 'log'), 

3459 ('text', 'log'), 

3460 ('text', None), 

3461 ('all', None), 

3462 ): 

3463 bi = k.getBindingHelper(key, name, stroke, w) 

3464 if bi: 

3465 return bi 

3466 return None 

3467 #@+node:ekr.20180418105228.1: *7* getPaneBindingHelper 

3468 def getBindingHelper(self, key, name, stroke, w): 

3469 """Find a binding for the widget with the given name.""" 

3470 c, k = self.c, self 

3471 # 

3472 # Return if the pane's name doesn't match the event's widget. 

3473 state = k.unboundKeyAction 

3474 w_name = c.widget_name(w) 

3475 pane_matches = ( 

3476 name and w_name.startswith(name) or 

3477 key in ('command', 'insert', 'overwrite') and state == key or 

3478 key in ('text', 'all') and g.isTextWrapper(w) or 

3479 key in ('button', 'all') 

3480 ) 

3481 if not pane_matches: 

3482 return None 

3483 # 

3484 # Return if there is no binding at all. 

3485 d = k.masterBindingsDict.get(key, {}) 

3486 if not d: 

3487 return None 

3488 bi = d.get(stroke) 

3489 if not bi: 

3490 return None 

3491 # 

3492 # Ignore previous/next-line commands while editing headlines. 

3493 if ( 

3494 key == 'text' and 

3495 name == 'head' and 

3496 bi.commandName in ('previous-line', 'next-line') 

3497 ): 

3498 return None 

3499 # 

3500 # The binding has been found. 

3501 return bi 

3502 #@+node:ekr.20160409035115.1: *6* k.searchTree 

3503 def searchTree(self, char): 

3504 """Search all visible nodes for a headline starting with stroke.""" 

3505 if not char: 

3506 return 

3507 c = self.c 

3508 if not c.config.getBool('plain-key-outline-search'): 

3509 return 

3510 

3511 def match(p): 

3512 """Return True if p contains char.""" 

3513 s = p.h.lower() if char.islower() else p.h 

3514 return s.find(char) > -1 

3515 

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

3517 

3518 for p in (c.p, c.rootPosition()): 

3519 p = p.copy() 

3520 if p == c.p and match(p): 

3521 p.moveToVisNext(c) 

3522 while p: 

3523 if match(p): 

3524 c.selectPosition(p) 

3525 c.redraw() 

3526 return 

3527 p.moveToVisNext(c) 

3528 

3529 # Too confusing for the user. 

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

3531 

3532 # def match(p, pattern): 

3533 # s = p.h.lower() 

3534 # if pattern: 

3535 # m = pattern.search(s) 

3536 # found = (s.startswith(char) or 

3537 # m and m.group(2).lower().startswith(char)) 

3538 # else: 

3539 # found = s.find(char) > -1 

3540 # if found: 

3541 # c.selectPosition(p) 

3542 # c.redraw() 

3543 # return found 

3544 #@+node:ekr.20061031170011.3: *3* k.Minibuffer 

3545 # These may be overridden, but this code is now gui-independent. 

3546 #@+node:ekr.20061031170011.9: *4* k.extendLabel 

3547 def extendLabel(self, s, select=False, protect=False): 

3548 

3549 c, k, w = self.c, self, self.w 

3550 if not (w and s): 

3551 return 

3552 c.widgetWantsFocusNow(w) 

3553 w.insert('end', s) 

3554 if select: 

3555 i, j = k.getEditableTextRange() 

3556 w.setSelectionRange(i, j, insert=j) 

3557 if protect: 

3558 k.protectLabel() 

3559 #@+node:ekr.20061031170011.13: *4* k.getEditableTextRange 

3560 def getEditableTextRange(self): 

3561 k, w = self, self.w 

3562 s = w.getAllText() 

3563 i = len(k.mb_prefix) 

3564 j = len(s) 

3565 return i, j 

3566 #@+node:ekr.20061031170011.5: *4* k.getLabel 

3567 def getLabel(self, ignorePrompt=False): 

3568 k, w = self, self.w 

3569 if not w: 

3570 return '' 

3571 s = w.getAllText() 

3572 if ignorePrompt: 

3573 return s[len(k.mb_prefix) :] 

3574 return s or '' 

3575 #@+node:ekr.20080408060320.791: *4* k.killLine 

3576 def killLine(self, protect=True): 

3577 k = self 

3578 w = k.w 

3579 s = w.getAllText() 

3580 s = s[: len(k.mb_prefix)] 

3581 w.setAllText(s) 

3582 n = len(s) 

3583 w.setSelectionRange(n, n, insert=n) 

3584 if protect: 

3585 k.mb_prefix = s 

3586 #@+node:ekr.20061031131434.135: *4* k.minibufferWantsFocus 

3587 # def minibufferWantsFocus(self): 

3588 # c = self.c 

3589 # c.widgetWantsFocus(c.miniBufferWidget) 

3590 #@+node:ekr.20061031170011.6: *4* k.protectLabel 

3591 def protectLabel(self): 

3592 k, w = self, self.w 

3593 if not w: 

3594 return 

3595 k.mb_prefix = w.getAllText() 

3596 #@+node:ekr.20061031170011.7: *4* k.resetLabel 

3597 def resetLabel(self): 

3598 """Reset the minibuffer label.""" 

3599 k = self 

3600 c, w = k.c, k.w 

3601 k.setLabelGrey('') 

3602 k.mb_prefix = '' 

3603 if w: 

3604 w.setSelectionRange(0, 0, insert=0) 

3605 state = k.unboundKeyAction 

3606 if c.vim_mode and c.vimCommands: 

3607 c.vimCommands.show_status() 

3608 else: 

3609 k.setLabelBlue(label=f"{state.capitalize()} State") 

3610 #@+node:ekr.20080408060320.790: *4* k.selectAll 

3611 def selectAll(self): 

3612 """Select all the user-editable text of the minibuffer.""" 

3613 w = self.w 

3614 i, j = self.getEditableTextRange() 

3615 w.setSelectionRange(i, j, insert=j) 

3616 #@+node:ekr.20061031170011.8: *4* k.setLabel 

3617 def setLabel(self, s, protect=False): 

3618 """Set the label of the minibuffer.""" 

3619 c, k, w = self.c, self, self.w 

3620 if w: 

3621 # Support for the curses gui. 

3622 if hasattr(g.app.gui, 'set_minibuffer_label'): 

3623 g.app.gui.set_minibuffer_label(c, s) 

3624 w.setAllText(s) 

3625 n = len(s) 

3626 w.setSelectionRange(n, n, insert=n) 

3627 if protect: 

3628 k.mb_prefix = s 

3629 #@+node:ekr.20061031170011.10: *4* k.setLabelBlue 

3630 def setLabelBlue(self, label, protect=True): 

3631 """Set the minibuffer label.""" 

3632 k, w = self, self.w 

3633 if hasattr(g.app.gui, 'set_minibuffer_label'): 

3634 g.app.gui.set_minibuffer_label(self.c, label) 

3635 elif w: 

3636 w.setStyleClass('') # normal state, not warning or error 

3637 if label is not None: 

3638 k.setLabel(label, protect=protect) 

3639 #@+node:ekr.20061031170011.11: *4* k.setLabelGrey 

3640 def setLabelGrey(self, label=None): 

3641 k, w = self, self.w 

3642 if not w: 

3643 return 

3644 w.setStyleClass('minibuffer_warning') 

3645 if label is not None: 

3646 k.setLabel(label) 

3647 

3648 setLabelGray = setLabelGrey 

3649 #@+node:ekr.20080510153327.2: *4* k.setLabelRed 

3650 def setLabelRed(self, label=None, protect=False): 

3651 k, w = self, self.w 

3652 if not w: 

3653 return 

3654 w.setStyleClass('minibuffer_error') 

3655 if label is not None: 

3656 k.setLabel(label, protect) 

3657 #@+node:ekr.20140822051549.18298: *4* k.setStatusLabel 

3658 def setStatusLabel(self, s): 

3659 """ 

3660 Set the label to s. 

3661 

3662 Use k.setStatusLabel, not k.setLael, to report the status of a Leo 

3663 command. This allows the option to use g.es instead of the minibuffer 

3664 to report status. 

3665 """ 

3666 k = self 

3667 k.setLabel(s, protect=False) 

3668 #@+node:ekr.20061031170011.12: *4* k.updateLabel 

3669 def updateLabel(self, event): 

3670 """ 

3671 Mimic what would happen with the keyboard and a Text editor 

3672 instead of plain accumulation. 

3673 """ 

3674 c, k, w = self.c, self, self.w 

3675 if not event: 

3676 return 

3677 ch, stroke = event.char, event.stroke 

3678 if ch in "\n\r": 

3679 return 

3680 if stroke and not k.isPlainKey(stroke): 

3681 return # #2041. 

3682 c.widgetWantsFocusNow(w) 

3683 i, j = w.getSelectionRange() 

3684 ins = w.getInsertPoint() 

3685 if i != j: 

3686 w.delete(i, j) 

3687 if ch == '\b': 

3688 s = w.getAllText() 

3689 if len(s) > len(k.mb_prefix): 

3690 w.delete(i - 1) 

3691 i -= 1 

3692 else: 

3693 w.insert(ins, ch) 

3694 i = ins + 1 

3695 #@+node:ekr.20120208064440.10190: *3* k.Modes 

3696 #@+node:ekr.20061031131434.100: *4* k.addModeCommands (enterModeCallback) 

3697 def addModeCommands(self): 

3698 """Add commands created by @mode settings to c.commandsDict.""" 

3699 c, k = self.c, self 

3700 d = g.app.config.modeCommandsDict # Keys are command names: enter-x-mode. 

3701 # Create the callback functions and update c.commandsDict. 

3702 for key in d.keys(): 

3703 # pylint: disable=cell-var-from-loop 

3704 

3705 def enterModeCallback(event=None, name=key): 

3706 k.enterNamedMode(event, name) 

3707 

3708 c.commandsDict[key] = enterModeCallback 

3709 #@+node:ekr.20061031131434.157: *4* k.badMode 

3710 def badMode(self, modeName): 

3711 k = self 

3712 k.clearState() 

3713 if modeName.endswith('-mode'): 

3714 modeName = modeName[:-5] 

3715 k.setLabelGrey(f"@mode {modeName} is not defined (or is empty)") 

3716 #@+node:ekr.20061031131434.158: *4* k.createModeBindings 

3717 def createModeBindings(self, modeName, d, w): 

3718 """Create mode bindings for the named mode using dictionary d for w, a text widget.""" 

3719 c, k = self.c, self 

3720 assert d.name().endswith('-mode') 

3721 for commandName in d.keys(): 

3722 if commandName in ('*entry-commands*', '*command-prompt*'): 

3723 # These are special-purpose dictionary entries. 

3724 continue 

3725 func = c.commandsDict.get(commandName) 

3726 if not func: 

3727 g.es_print('no such command:', commandName, 'Referenced from', modeName) 

3728 continue 

3729 aList = d.get(commandName, []) 

3730 for bi in aList: 

3731 stroke = bi.stroke 

3732 # Important: bi.val is canonicalized. 

3733 if stroke and stroke not in ('None', 'none', None): 

3734 assert g.isStroke(stroke) 

3735 k.makeMasterGuiBinding(stroke) 

3736 # Create the entry for the mode in k.masterBindingsDict. 

3737 # Important: this is similar, but not the same as k.bindKeyToDict. 

3738 # Thus, we should **not** call k.bindKey here! 

3739 d2 = k.masterBindingsDict.get(modeName, {}) 

3740 d2[stroke] = g.BindingInfo( 

3741 kind=f"mode<{modeName}>", 

3742 commandName=commandName, 

3743 func=func, 

3744 nextMode=bi.nextMode, 

3745 stroke=stroke) 

3746 k.masterBindingsDict[modeName] = d2 

3747 #@+node:ekr.20120208064440.10179: *4* k.endMode 

3748 def endMode(self): 

3749 c, k = self.c, self 

3750 w = g.app.gui.get_focus(c) 

3751 if w: 

3752 c.frame.log.deleteTab('Mode') # Changes focus to the body pane 

3753 k.inputModeName = None 

3754 k.clearState() 

3755 k.resetLabel() 

3756 k.showStateAndMode() # Restores focus. 

3757 if w: 

3758 c.widgetWantsFocusNow(w) 

3759 #@+node:ekr.20061031131434.160: *4* k.enterNamedMode 

3760 def enterNamedMode(self, event, commandName): 

3761 c, k = self.c, self 

3762 modeName = commandName[6:] 

3763 c.inCommand = False # Allow inner commands in the mode. 

3764 k.generalModeHandler(event, modeName=modeName) 

3765 #@+node:ekr.20061031131434.161: *4* k.exitNamedMode 

3766 @cmd('exit-named-mode') 

3767 def exitNamedMode(self, event=None): 

3768 """Exit an input mode.""" 

3769 k = self 

3770 if k.inState(): 

3771 k.endMode() 

3772 k.showStateAndMode() 

3773 #@+node:ekr.20120208064440.10199: *4* k.generalModeHandler 

3774 def generalModeHandler(self, event, 

3775 commandName=None, 

3776 func=None, 

3777 modeName=None, 

3778 nextMode=None, 

3779 prompt=None 

3780 ): 

3781 """Handle a mode defined by an @mode node in leoSettings.leo.""" 

3782 c, k = self.c, self 

3783 state = k.getState(modeName) 

3784 if state == 0: 

3785 k.inputModeName = modeName 

3786 k.modePrompt = prompt or modeName 

3787 k.modeWidget = event and event.widget 

3788 k.setState(modeName, 1, handler=k.generalModeHandler) 

3789 self.initMode(event, modeName) 

3790 # Careful: k.initMode can execute commands that will destroy a commander. 

3791 if g.app.quitting or not c.exists: 

3792 return 

3793 if not k.silentMode: 

3794 if c.config.getBool('showHelpWhenEnteringModes'): 

3795 k.modeHelp(event) 

3796 else: 

3797 c.frame.log.hideTab('Mode') 

3798 elif not func: 

3799 g.trace('No func: improper key binding') 

3800 else: 

3801 if commandName == 'mode-help': 

3802 func(event) 

3803 else: 

3804 self.endMode() 

3805 # New in 4.4.1 b1: pass an event describing the original widget. 

3806 if event: 

3807 event.w = event.widget = k.modeWidget 

3808 else: 

3809 event = g.app.gui.create_key_event(c, w=k.modeWidget) 

3810 func(event) 

3811 if g.app.quitting or not c.exists: 

3812 pass 

3813 elif nextMode in (None, 'none'): 

3814 # Do *not* clear k.inputModeName or the focus here. 

3815 # func may have put us in *another* mode. 

3816 pass 

3817 elif nextMode == 'same': 

3818 silent = k.silentMode 

3819 k.setState(modeName, 1, handler=k.generalModeHandler) 

3820 self.reinitMode(modeName) # Re-enter this mode. 

3821 k.silentMode = silent 

3822 else: 

3823 k.silentMode = False # All silent modes must do --> set-silent-mode. 

3824 self.initMode(event, nextMode) # Enter another mode. 

3825 #@+node:ekr.20061031131434.163: *4* k.initMode 

3826 def initMode(self, event, modeName): 

3827 

3828 c, k = self.c, self 

3829 if not modeName: 

3830 g.trace('oops: no modeName') 

3831 return 

3832 d = g.app.config.modeCommandsDict.get('enter-' + modeName) 

3833 if not d: 

3834 self.badMode(modeName) 

3835 return 

3836 k.modeBindingsDict = d 

3837 bi = d.get('*command-prompt*') 

3838 prompt = bi.kind if bi else modeName 

3839 k.inputModeName = modeName 

3840 k.silentMode = False 

3841 aList = d.get('*entry-commands*', []) 

3842 if aList: 

3843 for bi in aList: 

3844 commandName = bi.commandName 

3845 k.simulateCommand(commandName) 

3846 # Careful, the command can kill the commander. 

3847 if g.app.quitting or not c.exists: 

3848 return 

3849 # New in Leo 4.5: a startup command can immediately transfer to another mode. 

3850 if commandName.startswith('enter-'): 

3851 return 

3852 # Create bindings after we know whether we are in silent mode. 

3853 w = k.modeWidget if k.silentMode else k.w 

3854 k.createModeBindings(modeName, d, w) 

3855 k.showStateAndMode(prompt=prompt) 

3856 #@+node:ekr.20061031131434.165: *4* k.modeHelp & helper 

3857 @cmd('mode-help') 

3858 def modeHelp(self, event): 

3859 """ 

3860 The mode-help command. 

3861 

3862 A possible convention would be to bind <Tab> to this command in most modes, 

3863 by analogy with tab completion. 

3864 """ 

3865 c, k = self.c, self 

3866 c.endEditing() 

3867 if k.inputModeName: 

3868 d = g.app.config.modeCommandsDict.get('enter-' + k.inputModeName) 

3869 k.modeHelpHelper(d) 

3870 if not k.silentMode: 

3871 c.minibufferWantsFocus() 

3872 #@+node:ekr.20061031131434.166: *5* modeHelpHelper 

3873 def modeHelpHelper(self, d): 

3874 c, k = self.c, self 

3875 tabName = 'Mode' 

3876 c.frame.log.clearTab(tabName) 

3877 data, n = [], 0 

3878 for key in sorted(d.keys()): 

3879 if key in ('*entry-commands*', '*command-prompt*'): 

3880 pass 

3881 else: 

3882 aList = d.get(key) 

3883 for bi in aList: 

3884 stroke = bi.stroke 

3885 if stroke not in (None, 'None'): 

3886 s1 = key 

3887 s2 = k.prettyPrintKey(stroke) 

3888 n = max(n, len(s1)) 

3889 data.append((s1, s2),) 

3890 data.sort() 

3891 modeName = k.inputModeName.replace('-', ' ') 

3892 if modeName.endswith('mode'): 

3893 modeName = modeName[:-4].strip() 

3894 prompt = d.get('*command-prompt*') 

3895 if prompt: 

3896 g.es('', f"{prompt.kind.strip()}\n\n", tabName=tabName) 

3897 else: 

3898 g.es('', f"{modeName} mode\n\n", tabName=tabName) 

3899 # This isn't perfect in variable-width fonts. 

3900 for s1, s2 in data: 

3901 g.es('', '%*s %s' % (n, s1, s2), tabName=tabName) 

3902 #@+node:ekr.20061031131434.164: *4* k.reinitMode 

3903 def reinitMode(self, modeName): 

3904 k = self 

3905 d = k.modeBindingsDict 

3906 k.inputModeName = modeName 

3907 w = k.modeWidget if k.silentMode else k.w 

3908 k.createModeBindings(modeName, d, w) 

3909 if k.silentMode: 

3910 k.showStateAndMode() 

3911 else: 

3912 # Do not set the status line here. 

3913 k.setLabelBlue(modeName + ': ') # ,protect=True) 

3914 #@+node:ekr.20061031131434.181: *3* k.Shortcuts & bindings 

3915 #@+node:ekr.20061031131434.176: *4* k.computeInverseBindingDict 

3916 def computeInverseBindingDict(self): 

3917 k = self 

3918 d = {} # keys are minibuffer command names, values are shortcuts. 

3919 for stroke in k.bindingsDict: 

3920 assert g.isStroke(stroke), repr(stroke) 

3921 aList = k.bindingsDict.get(stroke, []) 

3922 for bi in aList: 

3923 shortcutList = k.bindingsDict.get(bi.commandName, []) 

3924 bi_list = k.bindingsDict.get( 

3925 stroke, g.BindingInfo(kind='dummy', pane='all')) 

3926 # Important: only bi.pane is required below. 

3927 for bi in bi_list: 

3928 pane = f"{bi.pane}:" 

3929 data = (pane, stroke) 

3930 if data not in shortcutList: 

3931 shortcutList.append(data) 

3932 d[bi.commandName] = shortcutList 

3933 return d 

3934 #@+node:ekr.20061031131434.179: *4* k.getShortcutForCommandName 

3935 def getStrokeForCommandName(self, commandName): 

3936 c, k = self.c, self 

3937 command = c.commandsDict.get(commandName) 

3938 if command: 

3939 for stroke, aList in k.bindingsDict.items(): 

3940 for bi in aList: 

3941 if bi.commandName == commandName: 

3942 return stroke 

3943 return None 

3944 #@+node:ekr.20090518072506.8494: *4* k.isFKey 

3945 def isFKey(self, stroke): 

3946 # k = self 

3947 if not stroke: 

3948 return False 

3949 assert isinstance(stroke, str) or g.isStroke(stroke) 

3950 s = stroke.s if g.isStroke(stroke) else stroke 

3951 s = s.lower() 

3952 return s.startswith('f') and len(s) <= 3 and s[1:].isdigit() 

3953 #@+node:ekr.20061031131434.182: *4* k.isPlainKey 

3954 def isPlainKey(self, stroke): 

3955 """Return true if the shortcut refers to a plain (non-Alt,non-Ctl) key.""" 

3956 if not stroke: 

3957 return False 

3958 if not g.isStroke(stroke): 

3959 # Happens during unit tests. 

3960 stroke = g.KeyStroke(stroke) 

3961 # 

3962 # altgr combos (Alt+Ctrl) are always plain keys 

3963 # g.KeyStroke does not handle this, because it has no "c" ivar. 

3964 # 

3965 if stroke.isAltCtrl() and not self.enable_alt_ctrl_bindings: 

3966 return True 

3967 return stroke.isPlainKey() 

3968 #@+node:ekr.20061031131434.191: *4* k.prettyPrintKey 

3969 def prettyPrintKey(self, stroke, brief=False): 

3970 

3971 if not stroke: 

3972 return '' 

3973 if not g.assert_is(stroke, g.KeyStroke): 

3974 return stroke 

3975 return stroke.prettyPrint() 

3976 #@+node:ekr.20110606004638.16929: *4* k.stroke2char 

3977 def stroke2char(self, stroke): 

3978 """ 

3979 Convert a stroke to an (insertable) char. 

3980 This method allows Leo to use strokes everywhere. 

3981 """ 

3982 if not stroke: 

3983 return '' 

3984 if not g.isStroke(stroke): 

3985 # vim commands pass a plain key. 

3986 stroke = g.KeyStroke(stroke) 

3987 return stroke.toInsertableChar() 

3988 #@+node:ekr.20061031131434.193: *3* k.States 

3989 #@+node:ekr.20061031131434.194: *4* k.clearState 

3990 def clearState(self): 

3991 """Clear the key handler state.""" 

3992 k = self 

3993 k.state.kind = None 

3994 k.state.n = None 

3995 k.state.handler = None 

3996 #@+node:ekr.20061031131434.196: *4* k.getState 

3997 def getState(self, kind): 

3998 k = self 

3999 val = k.state.n if k.state.kind == kind else 0 

4000 return val 

4001 #@+node:ekr.20061031131434.195: *4* k.getStateHandler 

4002 def getStateHandler(self): 

4003 return self.state.handler 

4004 #@+node:ekr.20061031131434.197: *4* k.getStateKind 

4005 def getStateKind(self): 

4006 return self.state.kind 

4007 #@+node:ekr.20061031131434.198: *4* k.inState 

4008 def inState(self, kind=None): 

4009 k = self 

4010 if kind: 

4011 return k.state.kind == kind and k.state.n is not None 

4012 return k.state.kind and k.state.n is not None 

4013 #@+node:ekr.20080511122507.4: *4* k.setDefaultInputState 

4014 def setDefaultInputState(self): 

4015 k = self 

4016 state = k.defaultUnboundKeyAction 

4017 k.setInputState(state) 

4018 #@+node:ekr.20110209093958.15411: *4* k.setEditingState 

4019 def setEditingState(self): 

4020 k = self 

4021 state = k.defaultEditingAction 

4022 k.setInputState(state) 

4023 #@+node:ekr.20061031131434.133: *4* k.setInputState 

4024 def setInputState(self, state, set_border=False): 

4025 k = self 

4026 k.unboundKeyAction = state 

4027 #@+node:ekr.20061031131434.199: *4* k.setState 

4028 def setState(self, kind, n, handler=None): 

4029 

4030 k = self 

4031 if kind and n is not None: 

4032 k.state.kind = kind 

4033 k.state.n = n 

4034 if handler: 

4035 k.state.handler = handler 

4036 else: 

4037 k.clearState() 

4038 # k.showStateAndMode() 

4039 #@+node:ekr.20061031131434.192: *4* k.showStateAndMode 

4040 def showStateAndMode(self, w=None, prompt=None, setFocus=True): 

4041 """Show the state and mode at the start of the minibuffer.""" 

4042 c, k = self.c, self 

4043 state = k.unboundKeyAction 

4044 mode = k.getStateKind() 

4045 if not g.app.gui: 

4046 return 

4047 if not w: 

4048 if hasattr(g.app.gui, 'set_minibuffer_label'): 

4049 pass # we don't need w 

4050 else: 

4051 w = g.app.gui.get_focus(c) 

4052 if not w: 

4053 return 

4054 isText = g.isTextWrapper(w) 

4055 # This fixes a problem with the tk gui plugin. 

4056 if mode and mode.lower().startswith('isearch'): 

4057 return 

4058 wname = g.app.gui.widget_name(w).lower() 

4059 # Get the wrapper for the headline widget. 

4060 if wname.startswith('head'): 

4061 if hasattr(c.frame.tree, 'getWrapper'): 

4062 if hasattr(w, 'widget'): 

4063 w2 = w.widget 

4064 else: 

4065 w2 = w 

4066 w = c.frame.tree.getWrapper(w2, item=None) 

4067 isText = bool(w) # A benign hack. 

4068 if mode: 

4069 if mode in ('getArg', 'getFileName', 'full-command'): 

4070 s = None 

4071 elif prompt: 

4072 s = prompt 

4073 else: 

4074 mode = mode.strip() 

4075 if mode.endswith('-mode'): 

4076 mode = mode[:-5] 

4077 s = f"{mode.capitalize()} Mode" 

4078 elif c.vim_mode and c.vimCommands: 

4079 c.vimCommands.show_status() 

4080 return 

4081 else: 

4082 s = f"{state.capitalize()} State" 

4083 if c.editCommands.extendMode: 

4084 s = s + ' (Extend Mode)' 

4085 if s: 

4086 k.setLabelBlue(s) 

4087 if w and isText: 

4088 # k.showStateColors(inOutline,w) 

4089 k.showStateCursor(state, w) 

4090 # 2015/07/11: reset the status line. 

4091 if hasattr(c.frame.tree, 'set_status_line'): 

4092 c.frame.tree.set_status_line(c.p) 

4093 #@+node:ekr.20110202111105.15439: *4* k.showStateCursor 

4094 def showStateCursor(self, state, w): 

4095 pass 

4096 #@-others 

4097#@+node:ekr.20120208064440.10150: ** class ModeInfo 

4098class ModeInfo: 

4099 

4100 def __repr__(self): 

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

4102 

4103 __str__ = __repr__ 

4104 #@+others 

4105 #@+node:ekr.20120208064440.10193: *3* mode_i. ctor 

4106 def __init__(self, c, name, aList): 

4107 

4108 self.c = c 

4109 self.d = {} # The bindings in effect for this mode. 

4110 # Keys are names of (valid) command names, values are BindingInfo objects. 

4111 self.entryCommands = [] 

4112 # A list of BindingInfo objects. 

4113 self.k = c.k 

4114 self.name = self.computeModeName(name) 

4115 self.prompt = self.computeModePrompt(self.name) 

4116 self.init(name, aList) 

4117 #@+node:ekr.20120208064440.10152: *3* mode_i.computeModeName 

4118 def computeModeName(self, name): 

4119 s = name.strip().lower() 

4120 j = s.find(' ') 

4121 if j > -1: 

4122 s = s[:j] 

4123 if s.endswith('mode'): 

4124 s = s[:-4].strip() 

4125 if s.endswith('-'): 

4126 s = s[:-1] 

4127 i = s.find('::') 

4128 if i > -1: 

4129 # The actual mode name is everything up to the "::" 

4130 # The prompt is everything after the prompt. 

4131 s = s[:i] 

4132 return s + '-mode' 

4133 #@+node:ekr.20120208064440.10156: *3* mode_i.computeModePrompt 

4134 def computeModePrompt(self, name): 

4135 assert name == self.name 

4136 s = 'enter-' + name.replace(' ', '-') 

4137 i = s.find('::') 

4138 if i > -1: 

4139 # The prompt is everything after the '::' 

4140 prompt = s[i + 2 :].strip() 

4141 else: 

4142 prompt = s 

4143 return prompt 

4144 #@+node:ekr.20120208064440.10160: *3* mode_i.createModeBindings 

4145 def createModeBindings(self, w): 

4146 """Create mode bindings for w, a text widget.""" 

4147 c, d, k, modeName = self.c, self.d, self.k, self.name 

4148 for commandName in d: 

4149 func = c.commandsDict.get(commandName) 

4150 if not func: 

4151 g.es_print(f"no such command: {commandName} Referenced from {modeName}") 

4152 continue 

4153 aList = d.get(commandName, []) 

4154 for bi in aList: 

4155 stroke = bi.stroke 

4156 # Important: bi.val is canonicalized. 

4157 if stroke and stroke not in ('None', 'none', None): 

4158 assert g.isStroke(stroke) 

4159 k.makeMasterGuiBinding(stroke) 

4160 # Create the entry for the mode in k.masterBindingsDict. 

4161 # Important: this is similar, but not the same as k.bindKeyToDict. 

4162 # Thus, we should **not** call k.bindKey here! 

4163 d2 = k.masterBindingsDict.get(modeName, {}) 

4164 d2[stroke] = g.BindingInfo( 

4165 kind=f"mode<{modeName}>", 

4166 commandName=commandName, 

4167 func=func, 

4168 nextMode=bi.nextMode, 

4169 stroke=stroke) 

4170 k.masterBindingsDict[modeName] = d2 

4171 #@+node:ekr.20120208064440.10195: *3* mode_i.createModeCommand 

4172 def createModeCommand(self): 

4173 c = self.c 

4174 key = 'enter-' + self.name.replace(' ', '-') 

4175 

4176 def enterModeCallback(event=None, self=self): 

4177 self.enterMode() 

4178 

4179 c.commandsDict[key] = f = enterModeCallback 

4180 g.trace('(ModeInfo)', f.__name__, key, 

4181 'len(c.commandsDict.keys())', len(list(c.commandsDict.keys()))) 

4182 #@+node:ekr.20120208064440.10180: *3* mode_i.enterMode 

4183 def enterMode(self): 

4184 

4185 c, k = self.c, self.k 

4186 c.inCommand = False # Allow inner commands in the mode. 

4187 event = None 

4188 k.generalModeHandler(event, modeName=self.name) 

4189 #@+node:ekr.20120208064440.10153: *3* mode_i.init 

4190 def init(self, name, dataList): 

4191 """aList is a list of tuples (commandName,bi).""" 

4192 c, d, modeName = self.c, self.d, self.name 

4193 for name, bi in dataList: 

4194 if not name: 

4195 # An entry command: put it in the special *entry-commands* key. 

4196 self.entryCommands.append(bi) 

4197 elif bi is not None: 

4198 # A regular shortcut. 

4199 bi.pane = modeName 

4200 aList = d.get(name, []) 

4201 # Important: use previous bindings if possible. 

4202 key2, aList2 = c.config.getShortcut(name) 

4203 aList3 = [z for z in aList2 if z.pane != modeName] 

4204 if aList3: 

4205 aList.extend(aList3) 

4206 aList.append(bi) 

4207 d[name] = aList 

4208 #@+node:ekr.20120208064440.10158: *3* mode_i.initMode 

4209 def initMode(self): 

4210 

4211 c, k = self.c, self.c.k 

4212 k.inputModeName = self.name 

4213 k.silentMode = False 

4214 for bi in self.entryCommands: 

4215 commandName = bi.commandName 

4216 k.simulateCommand(commandName) 

4217 # Careful, the command can kill the commander. 

4218 if g.app.quitting or not c.exists: 

4219 return 

4220 # New in Leo 4.5: a startup command can immediately transfer to another mode. 

4221 if commandName.startswith('enter-'): 

4222 return 

4223 # Create bindings after we know whether we are in silent mode. 

4224 w = k.modeWidget if k.silentMode else k.w 

4225 k.createModeBindings(self.name, self.d, w) 

4226 k.showStateAndMode(prompt=self.name) 

4227 #@-others 

4228#@-others 

4229#@@language python 

4230#@@tabwidth -4 

4231#@@pagewidth 70 

4232#@-leo