Coverage for C:\Repos\leo-editor\leo\plugins\mod_scripting.py: 10%

781 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

1#@+leo-ver=5-thin 

2#@+node:ekr.20060328125248: * @file ../plugins/mod_scripting.py 

3#@+<< mod_scripting docstring >> 

4#@+node:ekr.20060328125248.1: ** << mod_scripting docstring >> 

5r"""This plugin script buttons and eval* commands. 

6 

7Overview of script buttons 

8-------------------------- 

9 

10This plugin puts buttons in the icon area. Depending on settings the plugin will 

11create the 'Run Script', the 'Script Button' and the 'Debug Script' buttons. 

12 

13The 'Run Script' button is simply another way of doing the Execute Script 

14command: it executes the selected text of the presently selected node, or the 

15entire text if no text is selected. 

16 

17The 'Script Button' button creates *another* button in the icon area every time 

18you push it. The name of the button is the headline of the presently selected 

19node. Hitting this *newly created* button executes the button's script. 

20 

21For example, to run a script on any part of an outline do the following: 

22 

231. Select the node containing the script. 

242. Press the scriptButton button. This will create a new button. 

253. Select the node on which you want to run the script. 

264. Push the *new* button. 

27 

28Script buttons create commands 

29------------------------------ 

30 

31For every @button node, this plugin creates two new minibuffer commands: x and 

32delete-x-button, where x is the 'cleaned' name of the button. The 'x' command is 

33equivalent to pushing the script button. 

34 

35 

36Global buttons and commands 

37--------------------------- 

38 

39You can specify **global buttons** in leoSettings.leo or myLeoSettings.leo by 

40putting \@button nodes as children of an @buttons node in an \@settings trees. 

41Such buttons are included in all open .leo (in a slightly different color). 

42Actually, you can specify global buttons in any .leo file, but \@buttons nodes 

43affect all later opened .leo files so usually you would define global buttons in 

44leoSettings.leo or myLeoSettings.leo. 

45 

46The cleaned name of an @button node is the headline text of the button with: 

47 

48- Leading @button or @command removed, 

49- @key and all following text removed, 

50- @args and all following text removed, 

51- @color and all following text removed, 

52- all non-alphanumeric characters converted to a single '-' characters. 

53 

54Thus, cleaning headline text converts it to a valid minibuffer command name. 

55 

56You can delete a script button by right-clicking on it, or by 

57executing the delete-x-button command. 

58 

59.. The 'Debug Script' button runs a script using an external debugger. 

60 

61This plugin optionally scans for @script nodes whenever a .leo file is opened. 

62Such @script nodes cause a script to be executed when opening a .leo file. 

63They are security risks, and are never enabled by default. 

64 

65Settings 

66-------- 

67 

68You can specify the following options in myLeoSettings.leo. See the node: 

69@settings-->Plugins-->scripting plugin. Recommended defaults are shown:: 

70 

71 @bool scripting-at-button-nodes = True 

72 True: adds a button for every @button node. 

73 

74 @bool scripting-at-rclick-nodes = False 

75 True: define a minibuffer command for every @rclick node. 

76 

77 @bool scripting-at-commands-nodes = True 

78 True: define a minibuffer command for every @command node. 

79 

80 @bool scripting-at-plugin-nodes = False 

81 True: dynamically loads plugins in @plugin nodes when a window is created. 

82 

83 @bool scripting-at-script-nodes = False 

84 True: dynamically executes script in @script nodes when a window is created. 

85 This is dangerous! 

86 

87 @bool scripting-create-debug-button = False 

88 True: create Debug Script button. 

89 

90 @bool scripting-create-run-script-button = False 

91 True: create Run Script button. 

92 Note: The plugin creates the press-run-script-button regardless of this setting. 

93 

94 @bool scripting-create-script-button-button = True 

95 True: create Script Button button in icon area. 

96 Note: The plugin creates the press-script-button-button regardless of this setting. 

97 

98 @int scripting-max-button-size = 18 

99 The maximum length of button names: longer names are truncated. 

100 

101Shortcuts for script buttons 

102---------------------------- 

103 

104You can bind key shortcuts to @button and @command nodes as follows: 

105 

106@button name @key=shortcut 

107 

108 Binds the shortcut to the script in the script button. The button's name is 

109 'name', but you can see the full headline in the status line when you move the 

110 mouse over the button. 

111 

112@command name @key=shortcut 

113 

114 Creates a new minibuffer command and binds shortcut to it. As with @buffer 

115 nodes, the name of the command is the cleaned name of the headline. 

116 

117Binding arguments to script buttons with @args 

118---------------------------------------------- 

119 

120You can run @button and @command scripts with sys.argv initialized to string values using @args. 

121For example:: 

122 

123 @button test-args @args = a,b,c 

124 

125will set sys.argv to ['a', 'b', 'c']. 

126 

127You can set the background color of buttons created by @button nodes by using @color. 

128For example:: 

129 

130 @button my button @key=Ctrl+Alt+1 @color=white @args=a,b,c 

131 

132This creates a button named 'my-button', with a color of white, a keyboard shortcut 

133of Ctrl+Alt+1, and sets sys.argv to ['a', 'b', 'c'] within the context of the script. 

134 

135Eval Commands 

136------------- 

137 

138The mod_scripting plugin creates the following 5 eval* commands: 

139 

140eval 

141---- 

142 

143Evaluates the selected text, if any, and remember the result in c.vs, a global namespace. 

144For example:: 

145 

146 a = 10 

147 

148sets: 

149 

150 c.vs['a'] = 10 

151 

152This command prints the result of the last expression or assignment in the log pane 

153and select the next line of the body pane. Handy for executing line by line. 

154 

155eval-last 

156--------- 

157 

158Inserts the result of the last eval in the body. 

159Suppose you have this text:: 

160 

161 The cat is 7 years, or 7*365 days old. 

162 

163To replace 7*365 with 2555, do the following:: 

164 

165 select 7*367 

166 eval 

167 delete 7*365 

168 do eval-last 

169 

170eval-replace 

171------------ 

172 

173Evaluates the expression and replaces it with the computed value. 

174For example, the example above can be done as follows:: 

175 

176 

177 select 7*367 

178 eval-replace 

179 

180eval-last-pretty 

181---------------- 

182 

183Like eval-last, but format with pprint.pformat. 

184 

185eval-block 

186---------- 

187 

188Evaluates a series of blocks of code in the body, separated like this:: 

189 

190 # >>> 

191 code to run 

192 # <<< 

193 output of code 

194 # >>> 

195 code to run 

196 # <<< 

197 output of code 

198 ... 

199 

200For example:: 

201 

202 import datetime 

203 datetime.datetime.now() 

204 # >>> 

205 2018-03-21 21:46:13.582835 

206 # <<< 

207 datetime.datetime.now()+datetime.timedelta(days=1000) 

208 # >>> 

209 2020-12-15 21:46:34.403814 

210 # <<< 

211 

212eval-block inserts the separators, blocks can be re-run by placing the cursor in 

213them and doing eval-block, and the cursor is placed in the next block, so you 

214can go back up, change something, then quickly re-execute everything. 

215 

216Acknowledgements 

217---------------- 

218 

219This plugin is based on ideas from e's dynabutton plugin, possibly the 

220most brilliant idea in Leo's history. 

221""" 

222#@-<< mod_scripting docstring >> 

223#@+<< imports >> 

224#@+node:ekr.20060328125248.2: ** << imports >> 

225import pprint 

226import re 

227import sys 

228import textwrap 

229from typing import Any, Dict, List 

230from leo.core import leoGlobals as g 

231from leo.core import leoColor 

232from leo.core import leoGui 

233#@-<< imports >> 

234 

235#@+others 

236#@+node:ekr.20210228135810.1: ** cmd decorator 

237def eval_cmd(name): 

238 """Command decorator for the EvalController class.""" 

239 return g.new_cmd_decorator(name, ['c', 'evalController',]) 

240#@+node:ekr.20180328085010.1: ** Top level (mod_scripting) 

241#@+node:tbrown.20140819100840.37719: *3* build_rclick_tree (mod_scripting.py) 

242def build_rclick_tree(command_p, rclicks=None, top_level=False): 

243 """ 

244 Return a list of top level RClicks for the button at command_p, which can be 

245 used later to add the rclick menus. 

246 

247 After building a list of @rclick children and following siblings of the 

248 @button this method applies itself recursively to each member of that list 

249 to handle submenus. 

250 

251 :Parameters: 

252 - `command_p`: node containing @button. May be None 

253 - `rclicks`: list of RClicks to add to, created if needed 

254 - `top_level`: is this the top level? 

255 """ 

256 # representation of an rclick node 

257 from collections import namedtuple 

258 RClick = namedtuple('RClick', 'position,children') 

259 

260 at_others_pat = re.compile(r'^\s*@others\b', re.MULTILINE) 

261 

262 def has_at_others(p): 

263 """Return True if p.b has a valid @others directive.""" 

264 # #2439: A much simplified version of g.get_directives_dict. 

265 if 'others' in g.globalDirectiveList: 

266 return bool(re.search(at_others_pat, p.b)) 

267 return False 

268 

269 # Called from QtIconBarClass.setCommandForButton. 

270 if rclicks is None: 

271 rclicks = [] 

272 if top_level: 

273 # command_p will be None for leoSettings.leo and myLeoSettings.leo. 

274 if command_p: 

275 if not has_at_others(command_p): 

276 rclicks.extend([ 

277 RClick( 

278 position=i.copy(), # -2 for top level entries, i.e. before "Remove button" 

279 children=[], 

280 ) 

281 for i in command_p.children() 

282 if i.h.startswith('@rclick ') 

283 ]) 

284 for i in command_p.following_siblings(): 

285 if i.h.startswith('@rclick '): 

286 rclicks.append(RClick(position=i.copy(), children=[])) 

287 else: 

288 break 

289 for rc in rclicks: 

290 build_rclick_tree(rc.position, rc.children, top_level=False) 

291 else: # recursive mode below top level 

292 if not command_p: 

293 return [] 

294 if command_p.b.strip(): 

295 return [] # sub menus can't have body text 

296 for child in command_p.children(): 

297 # pylint: disable=no-member 

298 rc = RClick(position=child.copy(), children=[]) 

299 rclicks.append(rc) 

300 build_rclick_tree(rc.position, rc.children, top_level=False) 

301 return rclicks 

302#@+node:ekr.20060328125248.4: *3* init 

303def init(): 

304 """Return True if the plugin has loaded successfully.""" 

305 if g.app.gui is None: 

306 g.app.createQtGui(__file__) 

307 # This plugin is now gui-independent. 

308 ok = g.app.gui and g.app.gui.guiName() in ('qt', 'nullGui') 

309 if ok: 

310 sc = 'ScriptingControllerClass' 

311 if (not hasattr(g.app.gui, sc) or 

312 getattr(g.app.gui, sc) is leoGui.NullScriptingControllerClass 

313 ): 

314 setattr(g.app.gui, sc, ScriptingController) 

315 # Note: call onCreate _after_ reading the .leo file. 

316 # That is, the 'after-create-leo-frame' hook is too early! 

317 g.registerHandler(('new', 'open2'), onCreate) 

318 g.plugin_signon(__name__) 

319 return ok 

320#@+node:ekr.20060328125248.5: *3* onCreate 

321def onCreate(tag, keys): 

322 """Handle the onCreate event in the mod_scripting plugin.""" 

323 c = keys.get('c') 

324 if c: 

325 sc = g.app.gui.ScriptingControllerClass(c) 

326 c.theScriptingController = sc 

327 sc.createAllButtons() 

328 c.evalController = EvalController(c) 

329#@+node:ekr.20141031053508.7: ** class AtButtonCallback 

330class AtButtonCallback: 

331 """A class whose __call__ method is a callback for @button nodes.""" 

332 #@+others 

333 #@+node:ekr.20141031053508.9: *3* __init__ (AtButtonCallback) 

334 def __init__(self, controller, b, c, buttonText, docstring, gnx, script): 

335 """AtButtonCallback.__init__.""" 

336 self.b = b # A QButton. 

337 self.buttonText = buttonText # The text of the button. 

338 self.c = c # A Commander. 

339 self.controller = controller # A ScriptingController instance. 

340 self.gnx = gnx # Set if the script is defined in the local .leo file. 

341 self.script = script # The script defined in myLeoSettings.leo or leoSettings.leo 

342 self.source_c = c # For GetArgs.command_source. 

343 self.__doc__ = docstring # The docstring for this callback for g.getDocStringForFunction. 

344 #@+node:ekr.20141031053508.10: *3* __call__ (AtButtonCallback) 

345 def __call__(self, event=None): 

346 """AtButtonCallbgack.__call__. The callback for @button nodes.""" 

347 self.execute_script() 

348 #@+node:ekr.20141031053508.13: *3* __repr__ (AtButtonCallback) 

349 def __repr__(self): 

350 """AtButtonCallback.__repr__.""" 

351 c = self.c 

352 return 'AtButtonCallback %s gnx: %s len(script) %s' % ( 

353 c.shortFileName(), self.gnx, len(self.script or '')) 

354 #@+node:ekr.20150512041758.1: *3* __getattr__ (AtButtonCallback) 

355 def __getattr__(self, attr): 

356 """AtButtonCallback.__getattr__. Implement __name__.""" 

357 if attr == '__name__': 

358 return 'AtButtonCallback: %s' % self.gnx 

359 raise AttributeError # Returning None is not correct. 

360 #@+node:ekr.20170203043042.1: *3* AtButtonCallback.execute_script & helper 

361 def execute_script(self): 

362 """Execute the script associated with this button.""" 

363 script = self.find_script() 

364 if script: 

365 self.controller.executeScriptFromButton( 

366 b=self.b, 

367 buttonText=self.buttonText, 

368 p=None, 

369 script_gnx=self.gnx, 

370 script=script, 

371 ) 

372 #@+node:ekr.20180313171043.1: *4* AtButtonCallback.find_script 

373 def find_script(self): 

374 

375 gnx = self.gnx 

376 # First, search self.c for the gnx. 

377 for p in self.c.all_positions(): 

378 if p.gnx == gnx: 

379 script = self.controller.getScript(p) 

380 return script 

381 # See if myLeoSettings.leo is open. 

382 for c in g.app.commanders(): 

383 if c.shortFileName().endswith('myLeoSettings.leo'): 

384 break 

385 else: 

386 c = None 

387 if c: 

388 # Search myLeoSettings.leo file for the gnx. 

389 for p in c.all_positions(): 

390 if p.gnx == gnx: 

391 script = self.controller.getScript(p) 

392 return script 

393 return self.script 

394 #@-others 

395#@+node:ekr.20060328125248.6: ** class ScriptingController 

396class ScriptingController: 

397 """A class defining scripting commands.""" 

398 #@+others 

399 #@+node:ekr.20060328125248.7: *3* sc.ctor 

400 def __init__(self, c, iconBar=None): 

401 self.c = c 

402 self.gui = c.frame.gui 

403 getBool = c.config.getBool 

404 self.scanned = False 

405 kind = c.config.getString('debugger-kind') or 'idle' 

406 self.buttonsDict = {} # Keys are buttons, values are button names (strings). 

407 self.debuggerKind = kind.lower() 

408 # True: adds a button for every @button node. 

409 self.atButtonNodes = getBool('scripting-at-button-nodes') 

410 # True: define a minibuffer command for every @command node. 

411 self.atCommandsNodes = getBool('scripting-at-commands-nodes') 

412 # True: define a minibuffer command for every @rclick node. 

413 self.atRclickNodes = getBool('scripting-at-rclick-nodes') 

414 # True: dynamically loads plugins in @plugin nodes when a window is created. 

415 self.atPluginNodes = getBool('scripting-at-plugin-nodes') 

416 # # DANGEROUS! True: dynamically executes script in @script nodes when a window is created. 

417 self.atScriptNodes = getBool('scripting-at-script-nodes') 

418 # Do not allow this setting to be changed in local (non-settings) .leo files. 

419 if self.atScriptNodes and c.config.isLocalSetting('scripting-at-script-nodes', 'bool'): 

420 g.issueSecurityWarning('@bool scripting-at-script-nodes') 

421 # Restore the value in myLeoSettings.leo 

422 val = g.app.config.valueInMyLeoSettings('scripting-at-script-nodes') 

423 if val is None: 

424 val = False 

425 g.es('Restoring value to', val, color='red') 

426 self.atScriptNodes = val 

427 # True: create Debug Script button. 

428 self.createDebugButton = getBool('scripting-create-debug-button') 

429 # True: create Run Script button. 

430 self.createRunScriptButton = getBool('scripting-create-run-script-button') 

431 # True: create Script Button button. 

432 self.createScriptButtonButton = getBool('scripting-create-script-button-button') 

433 # Maximum length of button names. 

434 self.maxButtonSize = c.config.getInt('scripting-max-button-size') or 18 

435 if not iconBar: 

436 self.iconBar = c.frame.getIconBarObject() 

437 else: 

438 self.iconBar = iconBar 

439 # #74: problems with @button if defined in myLeoSettings.leo 

440 self.seen = set() # Set of gnx's (not vnodes!) that created buttons or commands. 

441 #@+node:ekr.20150401113822.1: *3* sc.Callbacks 

442 #@+node:ekr.20060328125248.23: *4* sc.addScriptButtonCommand 

443 def addScriptButtonCommand(self, event=None): 

444 """Called when the user presses the 'script-button' button or executes the script-button command.""" 

445 c = self.c 

446 p = c.p 

447 h = p.h 

448 buttonText = self.getButtonText(h) 

449 shortcut = self.getShortcut(h) 

450 statusLine = "Run Script: %s" % buttonText 

451 if shortcut: 

452 statusLine = statusLine + " @key=" + shortcut 

453 self.createLocalAtButtonHelper(p, h, statusLine, kind='script-button', verbose=True) 

454 c.bodyWantsFocus() 

455 #@+node:ekr.20060522105937.1: *4* sc.runDebugScriptCommand 

456 def runDebugScriptCommand(self, event=None): 

457 """Called when user presses the 'debug-script' button or executes the debug-script command.""" 

458 c = self.c 

459 p = c.p 

460 script = g.getScript(c, p, useSelectedText=True, useSentinels=False) 

461 if script: 

462 #@+<< set debugging if debugger is active >> 

463 #@+node:ekr.20060523084441: *5* << set debugging if debugger is active >> 

464 g.trace(self.debuggerKind) 

465 if self.debuggerKind == 'winpdb': 

466 try: 

467 import rpdb2 

468 debugging = rpdb2.g_debugger is not None 

469 except ImportError: 

470 debugging = False 

471 elif self.debuggerKind == 'idle': 

472 # import idlelib.Debugger.py as Debugger 

473 # debugging = Debugger.interacting 

474 debugging = True 

475 else: 

476 debugging = False 

477 #@-<< set debugging if debugger is active >> 

478 if debugging: 

479 #@+<< create leoScriptModule >> 

480 #@+node:ekr.20060524073716: *5* << create leoScriptModule >> (mod_scripting.py) 

481 target = g.os_path_join(g.app.loadDir, 'leoScriptModule.py') 

482 with open(target, 'w') as f: 

483 f.write('# A module holding the script to be debugged.\n') 

484 if self.debuggerKind == 'idle': 

485 # This works, but uses the lame pdb debugger. 

486 f.write('import pdb\n') 

487 f.write('pdb.set_trace() # Hard breakpoint.\n') 

488 elif self.debuggerKind == 'winpdb': 

489 f.write('import rpdb2\n') 

490 f.write('if rpdb2.g_debugger is not None: # don\'t hang if the debugger isn\'t running.\n') 

491 f.write(' rpdb2.start_embedded_debugger(pwd="",fAllowUnencrypted=True) # Hard breakpoint.\n') 

492 # f.write('# Remove all previous variables.\n') 

493 f.write('# Predefine c, g and p.\n') 

494 f.write('from leo.core import leoGlobals as g\n') 

495 f.write('c = g.app.scriptDict.get("c")\n') 

496 f.write('script_gnx = g.app.scriptDict.get("script_gnx")\n') 

497 f.write('p = c.p\n') 

498 f.write('# Actual script starts here.\n') 

499 f.write(script + '\n') 

500 #@-<< create leoScriptModule >> 

501 # pylint: disable=no-name-in-module 

502 g.app.scriptDict['c'] = c 

503 g.app.scriptDict = {'script_gnx': p.gnx} 

504 if 'leoScriptModule' in sys.modules.keys(): 

505 del sys.modules['leoScriptModule'] # Essential. 

506 # pylint: disable=import-error 

507 # This *will* exist. 

508 from leo.core import leoScriptModule 

509 assert leoScriptModule # for pyflakes. 

510 else: 

511 g.error('No debugger active') 

512 c.bodyWantsFocus() 

513 #@+node:ekr.20060328125248.21: *4* sc.runScriptCommand 

514 def runScriptCommand(self, event=None): 

515 """Called when user presses the 'run-script' button or executes the run-script command.""" 

516 c, p = self.c, self.c.p 

517 args = self.getArgs(p) 

518 g.app.scriptDict = {'script_gnx': p.gnx} 

519 c.executeScript(args=args, p=p, useSelectedText=True, silent=True) 

520 if 0: 

521 # Do not assume the script will want to remain in this commander. 

522 c.bodyWantsFocus() 

523 #@+node:ekr.20060328125248.8: *3* sc.createAllButtons 

524 def createAllButtons(self): 

525 """Scan for @button, @rclick, @command, @plugin and @script nodes.""" 

526 c = self.c 

527 if self.scanned: 

528 return # Defensive. 

529 self.scanned = True 

530 # 

531 # First, create standard buttons. 

532 if self.createRunScriptButton: 

533 self.createRunScriptIconButton() 

534 if self.createScriptButtonButton: 

535 self.createScriptButtonIconButton() 

536 if self.createDebugButton: 

537 self.createDebugIconButton() 

538 # 

539 # Next, create common buttons and commands. 

540 self.createCommonButtons() 

541 self.createCommonCommands() 

542 # 

543 # Handle all other nodes. 

544 d = { 

545 'button': self.handleAtButtonNode, 

546 'command': self.handleAtCommandNode, 

547 'plugin': self.handleAtPluginNode, 

548 'rclick': self.handleAtRclickNode, 

549 'script': self.handleAtScriptNode, 

550 } 

551 pattern = re.compile(r'^@(button|command|plugin|rclick|script)\b') 

552 p = c.rootPosition() 

553 while p: 

554 gnx = p.v.gnx 

555 if p.isAtIgnoreNode(): 

556 p.moveToNodeAfterTree() 

557 elif gnx in self.seen: 

558 # #657 

559 # if g.match_word(p.h, 0, '@rclick'): 

560 if p.h.startswith('@rlick'): 

561 self.handleAtRclickNode(p) 

562 p.moveToThreadNext() 

563 else: 

564 self.seen.add(gnx) 

565 m = pattern.match(p.h) 

566 if m: 

567 func = d.get(m.group(1)) 

568 func(p) 

569 p.moveToThreadNext() 

570 #@+node:ekr.20060328125248.24: *3* sc.createLocalAtButtonHelper 

571 def createLocalAtButtonHelper(self, p, h, statusLine, 

572 kind='at-button', 

573 verbose=True, 

574 ): 

575 """Create a button for a local @button node.""" 

576 c = self.c 

577 buttonText = self.cleanButtonText(h, minimal=True) 

578 args = self.getArgs(p) 

579 # We must define the callback *after* defining b, 

580 # so set both command and shortcut to None here. 

581 bg = self.getColor(h) 

582 b = self.createIconButton( 

583 args=args, 

584 text=h, 

585 command=None, 

586 statusLine=statusLine, 

587 kind=kind, 

588 bg=bg, 

589 ) 

590 if not b: 

591 return None 

592 # Now that b is defined we can define the callback. 

593 # Yes, executeScriptFromButton *does* use b (to delete b if requested by the script). 

594 docstring = g.getDocString(p.b).strip() 

595 cb = AtButtonCallback( 

596 controller=self, 

597 b=b, 

598 c=c, 

599 buttonText=buttonText, 

600 docstring=docstring, 

601 gnx=p.v.gnx, 

602 script=None, 

603 ) 

604 self.iconBar.setCommandForButton( 

605 button=b, 

606 command=cb, # This encapsulates the script. 

607 command_p=p and p.copy(), # This does exist. 

608 controller=self, 

609 gnx=p and p.gnx, 

610 script=None, 

611 ) 

612 # At last we can define the command and use the shortcut. 

613 # registerAllCommands recomputes the shortcut. 

614 self.registerAllCommands( 

615 args=self.getArgs(p), 

616 func=cb, 

617 h=h, 

618 pane='button', 

619 source_c=p.v.context, 

620 tag='local @button') 

621 return b 

622 #@+node:ekr.20060328125248.17: *3* sc.createIconButton (creates all buttons) 

623 def createIconButton(self, args, text, command, statusLine, bg=None, kind=None): 

624 """ 

625 Create one icon button. 

626 This method creates all scripting icon buttons. 

627 

628 - Creates the actual button and its balloon. 

629 - Adds the button to buttonsDict. 

630 - Registers command with the shortcut. 

631 - Creates x amd delete-x-button commands, where x is the cleaned button name. 

632 - Binds a right-click in the button to a callback that deletes the button. 

633 """ 

634 c = self.c 

635 # Create the button and add it to the buttons dict. 

636 commandName = self.cleanButtonText(text) 

637 # Truncate only the text of the button, not the command name. 

638 truncatedText = self.truncateButtonText(commandName) 

639 if not truncatedText.strip(): 

640 g.error('%s ignored: no cleaned text' % (text.strip() or '')) 

641 return None 

642 # Command may be None. 

643 b = self.iconBar.add(text=truncatedText, command=command, kind=kind) 

644 if not b: 

645 return None 

646 self.setButtonColor(b, bg) 

647 self.buttonsDict[b] = truncatedText 

648 if statusLine: 

649 self.createBalloon(b, statusLine) 

650 if command: 

651 self.registerAllCommands( 

652 args=args, 

653 func=command, 

654 h=text, 

655 pane='button', 

656 source_c=c, 

657 tag='icon button') 

658 

659 def deleteButtonCallback(event=None, self=self, b=b): 

660 self.deleteButton(b, event=event) 

661 

662 # Register the delete-x-button command. 

663 deleteCommandName = 'delete-%s-button' % commandName 

664 c.k.registerCommand( 

665 # allowBinding=True, 

666 commandName=deleteCommandName, 

667 func=deleteButtonCallback, 

668 pane='button', 

669 shortcut=None, 

670 ) 

671 # Reporting this command is way too annoying. 

672 return b 

673 #@+node:ekr.20060328125248.28: *3* sc.executeScriptFromButton 

674 def executeScriptFromButton(self, b, buttonText, p, script, script_gnx=None): 

675 """Execute an @button script in p.b or script.""" 

676 c = self.c 

677 if c.disableCommandsMessage: 

678 g.blue(c.disableCommandsMessage) 

679 return 

680 if not p and not script: 

681 g.trace('can not happen: no p and no script') 

682 return 

683 g.app.scriptDict = {'script_gnx': script_gnx} 

684 args = self.getArgs(p) 

685 if not script: 

686 script = self.getScript(p) 

687 c.executeScript(args=args, p=p, script=script, silent=True) 

688 # Remove the button if the script asks to be removed. 

689 if g.app.scriptDict.get('removeMe'): 

690 g.es("Removing '%s' button at its request" % buttonText) 

691 self.deleteButton(b) 

692 # Do *not* set focus here: the script may have changed the focus. 

693 # c.bodyWantsFocus() 

694 #@+node:ekr.20130912061655.11294: *3* sc.open_gnx 

695 def open_gnx(self, c, gnx): 

696 """ 

697 Find the node with the given gnx in c, myLeoSettings.leo and leoSettings.leo. 

698 If found, open the tab/outline and select the specified node. 

699 Return c,p of the found node. 

700 

701 Called only from a callback in QtIconBarClass.setCommandForButton. 

702 """ 

703 if not gnx: 

704 g.trace('can not happen: no gnx') 

705 # First, look in commander c. 

706 for p2 in c.all_positions(): 

707 if p2.gnx == gnx: 

708 return c, p2 

709 # Fix bug 74: problems with @button if defined in myLeoSettings.leo. 

710 for f in (c.openMyLeoSettings, c.openLeoSettings): 

711 c2 = f() # Open the settings file. 

712 if c2: 

713 for p2 in c2.all_positions(): 

714 if p2.gnx == gnx: 

715 return c2, p2 

716 c2.close() 

717 # Fix bug 92: restore the previously selected tab. 

718 if hasattr(c.frame, 'top'): 

719 c.frame.top.leo_master.select(c) 

720 return None, None # 2017/02/02. 

721 #@+node:ekr.20150401130207.1: *3* sc.Scripts, common 

722 # Important: common @button and @command nodes do **not** update dynamically! 

723 #@+node:ekr.20080312071248.1: *4* sc.createCommonButtons 

724 def createCommonButtons(self): 

725 """Handle all global @button nodes.""" 

726 c = self.c 

727 buttons = c.config.getButtons() or [] 

728 for z in buttons: 

729 # #2011 

730 p, script, rclicks = z 

731 gnx = p.v.gnx 

732 if gnx not in self.seen: 

733 self.seen.add(gnx) 

734 script = self.getScript(p) 

735 self.createCommonButton(p, script, rclicks) 

736 #@+node:ekr.20070926084600: *4* sc.createCommonButton (common @button) 

737 def createCommonButton(self, p, script, rclicks=None): 

738 """ 

739 Create a button in the icon area for a common @button node in an @setting 

740 tree. Binds button presses to a callback that executes the script. 

741 

742 Important: Common @button and @command scripts now *do* update 

743 dynamically provided that myLeoSettings.leo is open. Otherwise the 

744 callback executes the static script. 

745 

746 See https://github.com/leo-editor/leo-editor/issues/171 

747 """ 

748 c = self.c 

749 gnx = p.gnx 

750 args = self.getArgs(p) 

751 # Fix bug #74: problems with @button if defined in myLeoSettings.leo 

752 docstring = g.getDocString(p.b).strip() 

753 statusLine = docstring or 'Global script button' 

754 shortcut = self.getShortcut(p.h) # Get the shortcut from the @key field in the headline. 

755 if shortcut: 

756 statusLine = '%s = %s' % (statusLine.rstrip(), shortcut) 

757 # We must define the callback *after* defining b, 

758 # so set both command and shortcut to None here. 

759 bg = self.getColor(p.h) # #2024 

760 b = self.createIconButton( 

761 args=args, 

762 bg=bg, # #2024 

763 text=p.h, 

764 command=None, 

765 statusLine=statusLine, 

766 kind='at-button', 

767 ) 

768 if not b: 

769 return 

770 # Now that b is defined we can define the callback. 

771 # Yes, the callback *does* use b (to delete b if requested by the script). 

772 buttonText = self.cleanButtonText(p.h) 

773 cb = AtButtonCallback( 

774 b=b, 

775 buttonText=buttonText, 

776 c=c, 

777 controller=self, 

778 docstring=docstring, 

779 # #367: the gnx is needed for the Goto Script command. 

780 # Use gnx to search myLeoSettings.leo if it is open. 

781 gnx=gnx, 

782 script=script, 

783 ) 

784 # Now patch the button. 

785 self.iconBar.setCommandForButton( 

786 button=b, 

787 command=cb, # This encapsulates the script. 

788 command_p=p and p.copy(), # #567 

789 controller=self, 

790 gnx=gnx, # For the find-button function. 

791 script=script, 

792 ) 

793 self.handleRclicks(rclicks) 

794 # At last we can define the command. 

795 self.registerAllCommands( 

796 args=args, 

797 func=cb, 

798 h=p.h, 

799 pane='button', 

800 source_c=p.v.context, 

801 tag='@button') 

802 #@+node:ekr.20080312071248.2: *4* sc.createCommonCommands 

803 def createCommonCommands(self): 

804 """Handle all global @command nodes.""" 

805 c = self.c 

806 aList = c.config.getCommands() or [] 

807 for z in aList: 

808 p, script = z 

809 gnx = p.v.gnx 

810 if gnx not in self.seen: 

811 self.seen.add(gnx) 

812 script = self.getScript(p) 

813 self.createCommonCommand(p, script) 

814 #@+node:ekr.20150401130818.1: *4* sc.createCommonCommand (common @command) 

815 def createCommonCommand(self, p, script): 

816 """ 

817 Handle a single @command node. 

818 

819 Important: Common @button and @command scripts now *do* update 

820 dynamically provided that myLeoSettings.leo is open. Otherwise the 

821 callback executes the static script. 

822 

823 See https://github.com/leo-editor/leo-editor/issues/171 

824 """ 

825 c = self.c 

826 args = self.getArgs(p) 

827 commonCommandCallback = AtButtonCallback( 

828 b=None, 

829 buttonText=None, 

830 c=c, 

831 controller=self, 

832 docstring=g.getDocString(p.b).strip(), 

833 gnx=p.v.gnx, # Used to search myLeoSettings.leo if it is open. 

834 script=script, # Fallback when myLeoSettings.leo is not open. 

835 ) 

836 self.registerAllCommands( 

837 args=args, 

838 func=commonCommandCallback, 

839 h=p.h, 

840 pane='button', # Fix bug 416: use 'button', NOT 'command', and NOT 'all' 

841 source_c=p.v.context, 

842 tag='global @command', 

843 ) 

844 #@+node:ekr.20150401130313.1: *3* sc.Scripts, individual 

845 #@+node:ekr.20060328125248.12: *4* sc.handleAtButtonNode @button 

846 def handleAtButtonNode(self, p): 

847 """ 

848 Create a button in the icon area for an @button node. 

849 

850 An optional @key=shortcut defines a shortcut that is bound to the button's script. 

851 The @key=shortcut does not appear in the button's name, but 

852 it *does* appear in the statutus line shown when the mouse moves over the button. 

853 

854 An optional @color=colorname defines a color for the button's background. It does 

855 not appear in the status line nor the button name. 

856 """ 

857 h = p.h 

858 shortcut = self.getShortcut(h) 

859 docstring = g.getDocString(p.b).strip() 

860 statusLine = docstring if docstring else 'Local script button' 

861 if shortcut: 

862 statusLine = '%s = %s' % (statusLine, shortcut) 

863 g.app.config.atLocalButtonsList.append(p.copy()) 

864 # This helper is also called by the script-button callback. 

865 self.createLocalAtButtonHelper(p, h, statusLine, verbose=False) 

866 #@+node:ekr.20060328125248.10: *4* sc.handleAtCommandNode @command 

867 def handleAtCommandNode(self, p): 

868 """Handle @command name [@key[=]shortcut].""" 

869 c = self.c 

870 if not p.h.strip(): 

871 return 

872 args = self.getArgs(p) 

873 

874 def atCommandCallback(event=None, args=args, c=c, p=p.copy()): 

875 # pylint: disable=dangerous-default-value 

876 c.executeScript(args=args, p=p, silent=True) 

877 

878 # Fix bug 1251252: https://bugs.launchpad.net/leo-editor/+bug/1251252 

879 # Minibuffer commands created by mod_scripting.py have no docstrings 

880 

881 atCommandCallback.__doc__ = g.getDocString(p.b).strip() 

882 self.registerAllCommands( 

883 args=args, 

884 func=atCommandCallback, 

885 h=p.h, 

886 pane='button', # Fix # 416. 

887 source_c=p.v.context, 

888 tag='local @command') 

889 g.app.config.atLocalCommandsList.append(p.copy()) 

890 #@+node:ekr.20060328125248.13: *4* sc.handleAtPluginNode @plugin 

891 def handleAtPluginNode(self, p): 

892 """Handle @plugin nodes.""" 

893 tag = "@plugin" 

894 h = p.h 

895 assert g.match(h, 0, tag) 

896 # Get the name of the module. 

897 moduleOrFileName = h[len(tag) :].strip() 

898 if not self.atPluginNodes: 

899 g.warning("disabled @plugin: %s" % (moduleOrFileName)) 

900 # elif theFile in g.app.loadedPlugins: 

901 elif g.pluginIsLoaded(moduleOrFileName): 

902 g.warning("plugin already loaded: %s" % (moduleOrFileName)) 

903 else: 

904 g.loadOnePlugin(moduleOrFileName) 

905 #@+node:peckj.20131113130420.6851: *4* sc.handleAtRclickNode @rclick 

906 def handleAtRclickNode(self, p): 

907 """Handle @rclick name [@key[=]shortcut].""" 

908 c = self.c 

909 if not p.h.strip(): 

910 return 

911 args = self.getArgs(p) 

912 

913 def atCommandCallback(event=None, args=args, c=c, p=p.copy()): 

914 # pylint: disable=dangerous-default-value 

915 c.executeScript(args=args, p=p, silent=True) 

916 if p.b.strip(): 

917 self.registerAllCommands( 

918 args=args, 

919 func=atCommandCallback, 

920 h=p.h, 

921 pane='all', 

922 source_c=p.v.context, 

923 tag='local @rclick') 

924 g.app.config.atLocalCommandsList.append(p.copy()) 

925 #@+node:vitalije.20180224113123.1: *4* sc.handleRclicks 

926 def handleRclicks(self, rclicks): 

927 def handlerc(rc): 

928 if rc.children: 

929 for i in rc.children: 

930 handlerc(i) 

931 else: 

932 self.handleAtRclickNode(rc.position) 

933 for rc in rclicks: 

934 handlerc(rc) 

935 

936 #@+node:ekr.20060328125248.14: *4* sc.handleAtScriptNode @script 

937 def handleAtScriptNode(self, p): 

938 """Handle @script nodes.""" 

939 c = self.c 

940 tag = "@script" 

941 assert g.match(p.h, 0, tag) 

942 name = p.h[len(tag) :].strip() 

943 args = self.getArgs(p) 

944 if self.atScriptNodes: 

945 g.blue("executing script %s" % (name)) 

946 c.executeScript(args=args, p=p, useSelectedText=False, silent=True) 

947 else: 

948 g.warning("disabled @script: %s" % (name)) 

949 if 0: 

950 # Do not assume the script will want to remain in this commander. 

951 c.bodyWantsFocus() 

952 #@+node:ekr.20150401125747.1: *3* sc.Standard buttons 

953 #@+node:ekr.20060522105937: *4* sc.createDebugIconButton 'debug-script' 

954 def createDebugIconButton(self): 

955 """Create the 'debug-script' button and the debug-script command.""" 

956 self.createIconButton( 

957 args=None, 

958 text='debug-script', 

959 command=self.runDebugScriptCommand, 

960 statusLine='Debug script in selected node', 

961 kind='debug-script') 

962 #@+node:ekr.20060328125248.20: *4* sc.createRunScriptIconButton 'run-script' 

963 def createRunScriptIconButton(self): 

964 """Create the 'run-script' button and the run-script command.""" 

965 self.createIconButton( 

966 args=None, 

967 text='run-script', 

968 command=self.runScriptCommand, 

969 statusLine='Run script in selected node', 

970 kind='run-script', 

971 ) 

972 #@+node:ekr.20060328125248.22: *4* sc.createScriptButtonIconButton 'script-button' 

973 def createScriptButtonIconButton(self): 

974 """Create the 'script-button' button and the script-button command.""" 

975 self.createIconButton( 

976 args=None, 

977 text='script-button', 

978 command=self.addScriptButtonCommand, 

979 statusLine='Make script button from selected node', 

980 kind="script-button-button") 

981 #@+node:ekr.20061014075212: *3* sc.Utils 

982 #@+node:ekr.20060929135558: *4* sc.cleanButtonText 

983 def cleanButtonText(self, s, minimal=False): 

984 """ 

985 Clean the text following @button or @command so 

986 that it is a valid name of a minibuffer command. 

987 """ 

988 # #1121: Don't lowercase anything. 

989 if minimal: 

990 return s.replace(' ', '-').strip('-') 

991 for tag in ('@key', '@args', '@color',): 

992 i = s.find(tag) 

993 if i > -1: 

994 j = s.find('@', i + 1) 

995 if i < j: 

996 s = s[:i] + s[j:] 

997 else: 

998 s = s[:i] 

999 s = s.strip() 

1000 return s.replace(' ', '-').strip('-') 

1001 #@+node:ekr.20060522104419.1: *4* sc.createBalloon (gui-dependent) 

1002 def createBalloon(self, w, label): 

1003 'Create a balloon for a widget.' 

1004 if g.app.gui.guiName().startswith('qt'): 

1005 # w is a leoIconBarButton. 

1006 if hasattr(w, 'button'): 

1007 w.button.setToolTip(label) 

1008 #@+node:ekr.20060328125248.26: *4* sc.deleteButton 

1009 def deleteButton(self, button, **kw): 

1010 """Delete the given button. 

1011 This is called from callbacks, it is not a callback.""" 

1012 w = button 

1013 if button and self.buttonsDict.get(w): 

1014 del self.buttonsDict[w] 

1015 self.iconBar.deleteButton(w) 

1016 self.c.bodyWantsFocus() 

1017 #@+node:ekr.20080813064908.4: *4* sc.getArgs 

1018 def getArgs(self, p): 

1019 """Return the list of @args field of p.h.""" 

1020 args: List[str] = [] 

1021 if not p: 

1022 return args 

1023 h, tag = p.h, '@args' 

1024 i = h.find(tag) 

1025 if i > -1: 

1026 j = g.skip_ws(h, i + len(tag)) 

1027 # 2011/10/16: Make '=' sign optional. 

1028 if g.match(h, j, '='): 

1029 j += 1 

1030 if 0: 

1031 s = h[j + 1 :].strip() 

1032 else: # new logic 1/3/2014 Jake Peck 

1033 k = h.find('@', j + 1) 

1034 if k == -1: 

1035 k = len(h) 

1036 s = h[j:k].strip() 

1037 args = s.split(',') 

1038 args = [z.strip() for z in args] 

1039 # if args: g.trace(args) 

1040 return args 

1041 #@+node:ekr.20060328125248.15: *4* sc.getButtonText 

1042 def getButtonText(self, h): 

1043 """Returns the button text found in the given headline string""" 

1044 tag = "@button" 

1045 if g.match_word(h, 0, tag): 

1046 h = h[len(tag) :].strip() 

1047 for tag in ('@key', '@args', '@color',): 

1048 i = h.find(tag) 

1049 if i > -1: 

1050 j = h.find('@', i + 1) 

1051 if i < j: 

1052 h = h[:i] + h[j + 1 :] 

1053 else: 

1054 h = h[:i] 

1055 h = h.strip() 

1056 buttonText = h 

1057 # fullButtonText = buttonText 

1058 return buttonText 

1059 #@+node:peckj.20140103101946.10404: *4* sc.getColor 

1060 def getColor(self, h): 

1061 """Returns the background color from the given headline string""" 

1062 color = None 

1063 tag = '@color' 

1064 i = h.find(tag) 

1065 if i > -1: 

1066 j = g.skip_ws(h, i + len(tag)) 

1067 if g.match(h, j, '='): 

1068 j += 1 

1069 k = h.find('@', j + 1) 

1070 if k == -1: 

1071 k = len(h) 

1072 color = h[j:k].strip() 

1073 return color 

1074 #@+node:ekr.20060328125248.16: *4* sc.getShortcut 

1075 def getShortcut(self, h): 

1076 """Return the keyboard shortcut from the given headline string""" 

1077 shortcut = None 

1078 i = h.find('@key') 

1079 if i > -1: 

1080 j = g.skip_ws(h, i + len('@key')) 

1081 if g.match(h, j, '='): 

1082 j += 1 

1083 if 0: 

1084 shortcut = h[j:].strip() 

1085 else: # new logic 1/3/2014 Jake Peck 

1086 k = h.find('@', j + 1) 

1087 if k == -1: 

1088 k = len(h) 

1089 shortcut = h[j:k].strip() 

1090 return shortcut 

1091 #@+node:ekr.20150402042350.1: *4* sc.getScript 

1092 def getScript(self, p): 

1093 """Return the script composed from p and its descendants.""" 

1094 return ( 

1095 g.getScript(self.c, p, 

1096 useSelectedText=False, 

1097 forcePythonSentinels=True, 

1098 useSentinels=True, 

1099 )) 

1100 #@+node:ekr.20120301114648.9932: *4* sc.registerAllCommands 

1101 def registerAllCommands(self, args, func, h, pane, source_c=None, tag=None): 

1102 """Register @button <name> and @rclick <name> and <name>""" 

1103 c, k = self.c, self.c.k 

1104 trace = False and not g.unitTesting 

1105 shortcut = self.getShortcut(h) or '' 

1106 commandName = self.cleanButtonText(h) 

1107 if trace and not g.isascii(commandName): 

1108 g.trace(commandName) 

1109 # Register the original function. 

1110 k.registerCommand( 

1111 allowBinding=True, 

1112 commandName=commandName, 

1113 func=func, 

1114 pane=pane, 

1115 shortcut=shortcut, 

1116 ) 

1117 

1118 # 2013/11/13 Jake Peck: 

1119 # include '@rclick-' in list of tags 

1120 for prefix in ('@button-', '@command-', '@rclick-'): 

1121 if commandName.startswith(prefix): 

1122 commandName2 = commandName[len(prefix) :].strip() 

1123 # Create a *second* func, to avoid collision in c.commandsDict. 

1124 

1125 def registerAllCommandsCallback(event=None, func=func): 

1126 func() 

1127 

1128 # Fix bug 1251252: https://bugs.launchpad.net/leo-editor/+bug/1251252 

1129 # Minibuffer commands created by mod_scripting.py have no docstrings. 

1130 registerAllCommandsCallback.__doc__ = func.__doc__ 

1131 # Make sure we never redefine an existing commandName. 

1132 if commandName2 in c.commandsDict: 

1133 # A warning here would be annoying. 

1134 if trace: 

1135 g.trace('Already in commandsDict: %r' % commandName2) 

1136 else: 

1137 k.registerCommand( 

1138 commandName=commandName2, 

1139 func=registerAllCommandsCallback, 

1140 pane=pane, 

1141 shortcut=None, 

1142 ) 

1143 #@+node:ekr.20150402021505.1: *4* sc.setButtonColor 

1144 def setButtonColor(self, b, bg): 

1145 """Set the background color of Qt button b to bg.""" 

1146 if not bg: 

1147 return 

1148 if not bg.startswith('#'): 

1149 bg0 = bg 

1150 d = leoColor.leo_color_database 

1151 bg = d.get(bg.lower()) 

1152 if not bg: 

1153 g.trace('bad color? %s' % bg0) 

1154 return 

1155 try: 

1156 b.button.setStyleSheet("QPushButton{background-color: %s}" % (bg)) 

1157 except Exception: 

1158 # g.es_exception() 

1159 pass # Might not be a valid color. 

1160 #@+node:ekr.20061015125212: *4* sc.truncateButtonText 

1161 def truncateButtonText(self, s): 

1162 # 2011/10/16: Remove @button here only. 

1163 i = 0 

1164 while g.match(s, i, '@'): 

1165 i += 1 

1166 if g.match_word(s, i, 'button'): 

1167 i += 6 

1168 s = s[i:] 

1169 if self.maxButtonSize > 10: 

1170 s = s[:self.maxButtonSize] 

1171 if s.endswith('-'): 

1172 s = s[:-1] 

1173 s = s.strip('-') 

1174 return s.strip() 

1175 #@-others 

1176 

1177scriptingController = ScriptingController 

1178#@+node:ekr.20180328085038.1: ** class EvalController 

1179class EvalController: 

1180 """A class defining all eval-* commands.""" 

1181 #@+others 

1182 #@+node:ekr.20180328130835.1: *3* eval.Birth 

1183 def __init__(self, c): 

1184 """Ctor for EvalController class.""" 

1185 self.answers = [] 

1186 self.c = c 

1187 self.d: Dict[str, Any] = {} 

1188 self.globals_d: Dict[str, Any] = {'c': c, 'g': g, 'p': c.p} 

1189 self.locals_d: Dict[str, Any] = {} 

1190 self.legacy = c.config.getBool('legacy-eval', default=True) 

1191 if g.app.ipk: 

1192 # Use the IPython namespace. 

1193 self.c.vs = g.app.ipk.namespace 

1194 elif self.legacy: 

1195 self.c.vs = self.d 

1196 else: 

1197 self.c.vs = self.globals_d 

1198 # allow the auto-completer to complete in this namespace 

1199 # Updated by do_exec. 

1200 self.c.keyHandler.autoCompleter.namespaces.append(self.c.vs) 

1201 self.last_result = None 

1202 self.old_stderr = None 

1203 self.old_stdout = None 

1204 #@+node:ekr.20180328092221.1: *3* eval.Commands 

1205 #@+node:ekr.20180328085426.2: *4* eval 

1206 @eval_cmd("eval") 

1207 def eval_command(self, event): 

1208 #@+<< eval docstring >> 

1209 #@+node:ekr.20180328100519.1: *5* << eval docstring >> 

1210 """ 

1211 Execute the selected text, if any, or the line containing the cursor. 

1212 

1213 Select next line of text. 

1214 

1215 Tries hard to capture the result of from the last expression in the 

1216 selected text:: 

1217 

1218 import datetime 

1219 today = datetime.date.today() 

1220 

1221 will capture the value of ``today`` even though the last line is a 

1222 statement, not an expression. 

1223 

1224 Stores results in ``c.vs['_last']`` for insertion 

1225 into body by ``eval-last`` or ``eval-last-pretty``. 

1226 

1227 Removes common indentation (``textwrap.dedent()``) before executing, 

1228 allowing execution of indented code. 

1229 

1230 ``g``, ``c``, and ``p`` are available to executing code, assignments 

1231 are made in the ``c.vs`` namespace and persist for the life of ``c``. 

1232 """ 

1233 #@-<< eval docstring >> 

1234 c = self.c 

1235 if c == event.get('c'): 

1236 s = self.get_selected_lines() 

1237 if self.legacy and s is None: 

1238 return 

1239 # Updates self.last_answer if there is exactly one answer. 

1240 self.eval_text(s) 

1241 #@+node:ekr.20180328085426.3: *4* eval-block 

1242 @eval_cmd("eval-block") 

1243 def eval_block(self, event): 

1244 #@+<< eval-block docstring >> 

1245 #@+node:ekr.20180328100415.1: *5* << eval-block docstring >> 

1246 """ 

1247 In the body, "# >>>" marks the end of a code block, and "# <<<" marks 

1248 the end of an output block. E.g.:: 

1249 

1250 a = 2 

1251 # >>> 

1252 4 

1253 # <<< 

1254 b = 2.0*a 

1255 # >>> 

1256 4.0 

1257 # <<< 

1258 

1259 ``eval-block`` evaluates the current code block, either the code block 

1260 the cursor's in, or the code block preceding the output block the cursor's 

1261 in. Subsequent output blocks are marked "# >>> *" to show they may need 

1262 re-evaluation. 

1263 

1264 Note: you don't really need to type the "# >>>" and "# <<<" markers 

1265 because ``eval-block`` will add them as needed. So just type the 

1266 first code block and run ``eval-block``. 

1267 

1268 """ 

1269 #@-<< eval-block docstring >> 

1270 c = self.c 

1271 if c != event.get('c'): 

1272 return 

1273 pos = 0 

1274 lines = [] 

1275 current_seen = False 

1276 for current, source, output in self.get_blocks(): 

1277 lines.append(source) 

1278 lines.append("# >>>" + (" *" if current_seen else "")) 

1279 if current: 

1280 old_log = c.frame.log.logCtrl.getAllText() 

1281 self.eval_text(source) 

1282 new_log = c.frame.log.logCtrl.getAllText()[len(old_log) :] 

1283 lines.append(new_log.strip()) 

1284 if not self.legacy: 

1285 if self.last_result: 

1286 lines.append(self.last_result) 

1287 pos = len('\n'.join(lines)) + 7 

1288 current_seen = True 

1289 else: 

1290 lines.append(output) 

1291 lines.append("# <<<") 

1292 c.p.b = '\n'.join(lines) + '\n' 

1293 c.frame.body.wrapper.setInsertPoint(pos) 

1294 c.redraw() 

1295 c.bodyWantsFocusNow() 

1296 #@+node:ekr.20180328085426.5: *4* eval-last 

1297 @eval_cmd("eval-last") 

1298 def eval_last(self, event, text=None): 

1299 """ 

1300 Insert the last result from ``eval``. 

1301 

1302 Inserted as a string, so ``"1\n2\n3\n4"`` will cover four lines and 

1303 insert no quotes, for ``repr()`` style insertion use ``last-pretty``. 

1304 """ 

1305 c = self.c 

1306 if c != event.get('c'): 

1307 return 

1308 if self.legacy: 

1309 text = str(c.vs.get('_last')) 

1310 else: 

1311 if not text and not self.last_result: 

1312 return 

1313 if not text: 

1314 text = str(self.last_result) 

1315 w = c.frame.body.wrapper 

1316 i = w.getInsertPoint() 

1317 w.insert(i, text + '\n') 

1318 w.setInsertPoint(i + len(text) + 1) 

1319 c.setChanged() 

1320 #@+node:ekr.20180328085426.6: *4* eval-last-pretty 

1321 @eval_cmd("eval-last-pretty") 

1322 def vs_last_pretty(self, event): 

1323 """ 

1324 Insert the last result from ``eval``. 

1325 

1326 Formatted by ``pprint.pformat()``, so ``"1\n2\n3\n4"`` will appear as 

1327 '``"1\n2\n3\n4"``', see all ``last``. 

1328 """ 

1329 c = self.c 

1330 if c != event.get('c'): 

1331 return 

1332 if self.legacy: 

1333 text = str(c.vs.get('_last')) 

1334 else: 

1335 text = self.last_result 

1336 if text: 

1337 text = pprint.pformat(text) 

1338 self.eval_last(event, text=text) 

1339 #@+node:ekr.20180328085426.4: *4* eval-replace 

1340 @eval_cmd("eval-replace") 

1341 def eval_replace(self, event): 

1342 """ 

1343 Execute the selected text, if any. 

1344 Undoably replace it with the result. 

1345 """ 

1346 c = self.c 

1347 if c != event.get('c'): 

1348 return 

1349 w = c.frame.body.wrapper 

1350 s = w.getSelectedText() 

1351 if not s.strip(): 

1352 g.es_print('no selected text') 

1353 return 

1354 self.eval_text(s) 

1355 if self.legacy: 

1356 last = c.vs.get('_last') 

1357 else: 

1358 last = self.last_result 

1359 if not last: 

1360 return 

1361 s = pprint.pformat(last) 

1362 i, j = w.getSelectionRange() 

1363 new_text = c.p.b[:i] + s + c.p.b[j:] 

1364 bunch = c.undoer.beforeChangeNodeContents(c.p) 

1365 w.setAllText(new_text) 

1366 c.p.b = new_text 

1367 w.setInsertPoint(i + len(s)) 

1368 c.undoer.afterChangeNodeContents(c.p, 'Insert result', bunch) 

1369 c.setChanged() 

1370 #@+node:ekr.20180328151652.1: *3* eval.Helpers 

1371 #@+node:ekr.20180328090830.1: *4* eval.eval_text & helpers 

1372 def eval_text(self, s): 

1373 """Evaluate string s.""" 

1374 s = textwrap.dedent(s) 

1375 if not s.strip(): 

1376 return None 

1377 self.redirect() 

1378 if self.legacy: 

1379 blocks = re.split('\n(?=[^\\s])', s) 

1380 ans = self.old_exec(blocks, s) 

1381 self.show_legacy_answer(ans, blocks) 

1382 return ans # needed by mod_http 

1383 self.new_exec(s) 

1384 self.show_answers() 

1385 self.unredirect() 

1386 return None 

1387 #@+node:ekr.20180329130626.1: *5* eval.new_exec 

1388 def new_exec(self, s): 

1389 try: 

1390 self.answers = [] 

1391 self.locals_d = {} 

1392 exec(s, self.globals_d, self.locals_d) 

1393 for key in self.locals_d: 

1394 val = self.locals_d.get(key) 

1395 self.globals_d[key] = val 

1396 self.answers.append((key, val),) 

1397 if len(self.answers) == 1: 

1398 key, val = self.answers[0] 

1399 self.last_result = val 

1400 else: 

1401 self.last_result = None 

1402 except Exception: 

1403 g.es_exception() 

1404 #@+node:ekr.20180329130623.1: *5* eval.old_exec 

1405 def old_exec(self, blocks, txt): 

1406 

1407 # pylint: disable=eval-used 

1408 c = self.c 

1409 leo_globals = {'c': c, 'g': g, 'p': c.p} 

1410 all_done, ans = False, None 

1411 try: 

1412 # Execute all but the last 'block' 

1413 exec('\n'.join(blocks[:-1]), leo_globals, c.vs) # Compatible with Python 3.x. 

1414 all_done = False 

1415 except SyntaxError: 

1416 # Splitting the last block caused syntax error 

1417 try: 

1418 # Is the whole thing a single expression? 

1419 ans = eval(txt, leo_globals, c.vs) 

1420 except SyntaxError: 

1421 try: 

1422 exec(txt, leo_globals, c.vs) 

1423 except Exception: 

1424 g.es_exception() 

1425 all_done = True # Either way, the last block will be used. 

1426 if not all_done: # last block still needs using 

1427 try: 

1428 ans = eval(blocks[-1], leo_globals, c.vs) 

1429 except SyntaxError: 

1430 try: 

1431 exec(txt, leo_globals, c.vs) 

1432 except Exception: 

1433 g.es_exception() 

1434 return ans 

1435 #@+node:ekr.20180328130526.1: *5* eval.redirect & unredirect 

1436 def redirect(self): 

1437 c = self.c 

1438 if c.config.getBool('eval-redirect'): 

1439 self.old_stderr = g.stdErrIsRedirected() 

1440 self.old_stdout = g.stdOutIsRedirected() 

1441 if not self.old_stderr: 

1442 g.redirectStderr() 

1443 if not self.old_stdout: 

1444 g.redirectStdout() 

1445 

1446 def unredirect(self): 

1447 c = self.c 

1448 if c.config.getBool('eval-redirect'): 

1449 if not self.old_stderr: 

1450 g.restoreStderr() 

1451 if not self.old_stdout: 

1452 g.restoreStdout() 

1453 #@+node:ekr.20180328132748.1: *5* eval.show_answers 

1454 def show_answers(self): 

1455 """ Show all new values computed by do_exec.""" 

1456 if len(self.answers) > 1: 

1457 g.es('') 

1458 for answer in self.answers: 

1459 key, val = answer 

1460 g.es('%s = %s' % (key, val)) 

1461 #@+node:ekr.20180329154232.1: *5* eval.show_legacy_answer 

1462 def show_legacy_answer(self, ans, blocks): 

1463 

1464 cvs = self.c.vs 

1465 if ans is None: # see if last block was a simple "var =" assignment 

1466 key = blocks[-1].split('=', 1)[0].strip() 

1467 if key in cvs: 

1468 ans = cvs[key] 

1469 if ans is None: # see if whole text was a simple /multi-line/ "var =" assignment 

1470 key = blocks[0].split('=', 1)[0].strip() 

1471 if key in cvs: 

1472 ans = cvs[key] 

1473 cvs['_last'] = ans 

1474 if ans is not None: 

1475 # annoying to echo 'None' to the log during line by line execution 

1476 txt = str(ans) 

1477 lines = txt.split('\n') 

1478 if len(lines) > 10: 

1479 txt = '\n'.join(lines[:5] + ['<snip>'] + lines[-5:]) 

1480 if len(txt) > 500: 

1481 txt = txt[:500] + ' <truncated>' 

1482 g.es(txt) 

1483 return ans 

1484 #@+node:ekr.20180329125626.1: *4* eval.exec_then_eval (not used yet) 

1485 def exec_then_eval(self, code, ns): 

1486 # From Milan Melena. 

1487 import ast 

1488 block = ast.parse(code, mode='exec') 

1489 if block.body and isinstance(block.body[-1], ast.Expr): 

1490 last = ast.Expression(block.body.pop().value) 

1491 exec(compile(block, '<string>', mode='exec'), ns) 

1492 # pylint: disable=eval-used 

1493 return eval(compile(last, '<string>', mode='eval'), ns) 

1494 exec(compile(block, '<string>', mode='exec'), ns) 

1495 return "" 

1496 #@+node:tbrown.20170516194332.1: *4* eval.get_blocks 

1497 def get_blocks(self): 

1498 """get_blocks - iterate code blocks 

1499 

1500 :return: (current, source, output) 

1501 :rtype: (bool, str, str) 

1502 """ 

1503 c = self.c 

1504 pos = c.frame.body.wrapper.getInsertPoint() 

1505 chrs = 0 

1506 lines = c.p.b.split('\n') 

1507 block: Dict[str, List] = {'source': [], 'output': []} 

1508 reading = 'source' 

1509 seeking_current = True 

1510 # if the last non-blank line isn't the end of a possibly empty 

1511 # output block, make it one 

1512 if [i for i in lines if i.strip()][-1] != "# <<<": 

1513 lines.append("# <<<") 

1514 while lines: 

1515 line = lines.pop(0) 

1516 chrs += len(line) + 1 

1517 if line.startswith("# >>>"): 

1518 reading = 'output' 

1519 continue 

1520 if line.startswith("# <<<"): 

1521 current = seeking_current and (chrs >= pos + 1) 

1522 if current: 

1523 seeking_current = False 

1524 yield current, '\n'.join(block['source']), '\n'.join(block['output']) 

1525 block = {'source': [], 'output': []} 

1526 reading = 'source' 

1527 continue 

1528 block[reading].append(line) 

1529 #@+node:ekr.20180328145035.1: *4* eval.get_selected_lines 

1530 def get_selected_lines(self): 

1531 

1532 c, p = self.c, self.c.p 

1533 w = c.frame.body.wrapper 

1534 body = w.getAllText() 

1535 i = w.getInsertPoint() 

1536 if w.hasSelection(): 

1537 if self.legacy: 

1538 i1, i2 = w.getSelectionRange() 

1539 else: 

1540 j, k = w.getSelectionRange() 

1541 i1, junk = g.getLine(body, j) 

1542 junk, i2 = g.getLine(body, k) 

1543 s = body[i1:i2] 

1544 else: 

1545 if self.legacy: 

1546 k = w.getInsertPoint() 

1547 junk, i2 = g.getLine(body, k) 

1548 w.setSelectionRange(k, i2) 

1549 return None 

1550 i1, i2 = g.getLine(body, i) 

1551 s = body[i1:i2].strip() 

1552 # Select next line for next eval. 

1553 if self.legacy: 

1554 i = j = i2 

1555 j += 1 

1556 while j < len(body) and body[j] != '\n': 

1557 j += 1 

1558 w.setSelectionRange(i, j) 

1559 else: 

1560 if not body.endswith('\n'): 

1561 if i >= len(p.b): 

1562 i2 += 1 

1563 p.b = p.b + '\n' 

1564 ins = min(len(p.b), i2) 

1565 w.setSelectionRange(i1, ins, insert=ins, s=p.b) 

1566 return s 

1567 #@-others 

1568#@-others 

1569#@@language python 

1570#@@tabwidth -4 

1571#@-leo