##// END OF EJS Templates
ipy_leo: do not crash if _p has been deleted in push
Ville M. Vainio -
Show More
@@ -1,575 +1,575 b''
1 1 """ ILeo - Leo plugin for IPython
2 2
3 3
4 4 """
5 5 import IPython.ipapi
6 6 import IPython.genutils
7 7 import IPython.generics
8 8 from IPython.hooks import CommandChainDispatcher
9 9 import re
10 10 import UserDict
11 11 from IPython.ipapi import TryNext
12 12 import IPython.macro
13 13
14 14 def init_ipython(ipy):
15 15 """ This will be run by _ip.load('ipy_leo')
16 16
17 17 Leo still needs to run update_commander() after this.
18 18
19 19 """
20 20 global ip
21 21 ip = ipy
22 22 ip.set_hook('complete_command', mb_completer, str_key = '%mb')
23 23 ip.expose_magic('mb',mb_f)
24 24 ip.expose_magic('lee',lee_f)
25 25 ip.expose_magic('leoref',leoref_f)
26 26 expose_ileo_push(push_cl_node,100)
27 27 # this should be the LAST one that will be executed, and it will never raise TryNext
28 28 expose_ileo_push(push_ipython_script, 1000)
29 29 expose_ileo_push(push_plain_python, 100)
30 30 expose_ileo_push(push_ev_node, 100)
31 31 global wb
32 32 wb = LeoWorkbook()
33 33 ip.user_ns['wb'] = wb
34 34
35 35 show_welcome()
36 36
37 37
38 38 def update_commander(new_leox):
39 39 """ Set the Leo commander to use
40 40
41 41 This will be run every time Leo does ipython-launch; basically,
42 42 when the user switches the document he is focusing on, he should do
43 43 ipython-launch to tell ILeo what document the commands apply to.
44 44
45 45 """
46 46
47 47 global c,g
48 48 c,g = new_leox.c, new_leox.g
49 49 print "Set Leo Commander:",c.frame.getTitle()
50 50
51 51 # will probably be overwritten by user, but handy for experimentation early on
52 52 ip.user_ns['c'] = c
53 53 ip.user_ns['g'] = g
54 54 ip.user_ns['_leo'] = new_leox
55 55
56 56 new_leox.push = push_position_from_leo
57 57 run_leo_startup_node()
58 58
59 59 from IPython.external.simplegeneric import generic
60 60 import pprint
61 61
62 62 def es(s):
63 63 g.es(s, tabName = 'IPython')
64 64 pass
65 65
66 66 @generic
67 67 def format_for_leo(obj):
68 68 """ Convert obj to string representiation (for editing in Leo)"""
69 69 return pprint.pformat(obj)
70 70
71 71 @format_for_leo.when_type(list)
72 72 def format_list(obj):
73 73 return "\n".join(str(s) for s in obj)
74 74
75 75
76 76 attribute_re = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
77 77 def valid_attribute(s):
78 78 return attribute_re.match(s)
79 79
80 80 _rootnode = None
81 81 def rootnode():
82 82 """ Get ileo root node (@ipy-root)
83 83
84 84 if node has become invalid or has not been set, return None
85 85
86 86 Note that the root is the *first* @ipy-root item found
87 87 """
88 88 global _rootnode
89 89 if _rootnode is None:
90 90 return None
91 91 if c.positionExists(_rootnode.p):
92 92 return _rootnode
93 93 _rootnode = None
94 94 return None
95 95
96 96 def all_cells():
97 97 global _rootnode
98 98 d = {}
99 99 r = rootnode()
100 100 if r is not None:
101 101 nodes = r.p.children_iter()
102 102 else:
103 103 nodes = c.allNodes_iter()
104 104
105 105 for p in nodes:
106 106 h = p.headString()
107 107 if h.strip() == '@ipy-root':
108 108 # update root node (found it for the first time)
109 109 _rootnode = LeoNode(p)
110 110 # the next recursive call will use the children of new root
111 111 return all_cells()
112 112
113 113 if h.startswith('@a '):
114 114 d[h.lstrip('@a ').strip()] = p.parent().copy()
115 115 elif not valid_attribute(h):
116 116 continue
117 117 d[h] = p.copy()
118 118 return d
119 119
120 120 def eval_node(n):
121 121 body = n.b
122 122 if not body.startswith('@cl'):
123 123 # plain python repr node, just eval it
124 124 return ip.ev(n.b)
125 125 # @cl nodes deserve special treatment - first eval the first line (minus cl), then use it to call the rest of body
126 126 first, rest = body.split('\n',1)
127 127 tup = first.split(None, 1)
128 128 # @cl alone SPECIAL USE-> dump var to user_ns
129 129 if len(tup) == 1:
130 130 val = ip.ev(rest)
131 131 ip.user_ns[n.h] = val
132 132 es("%s = %s" % (n.h, repr(val)[:20] ))
133 133 return val
134 134
135 135 cl, hd = tup
136 136
137 137 xformer = ip.ev(hd.strip())
138 138 es('Transform w/ %s' % repr(xformer))
139 139 return xformer(rest, n)
140 140
141 141 class LeoNode(object, UserDict.DictMixin):
142 142 """ Node in Leo outline
143 143
144 144 Most important attributes (getters/setters available:
145 145 .v - evaluate node, can also be alligned
146 146 .b, .h - body string, headline string
147 147 .l - value as string list
148 148
149 149 Also supports iteration,
150 150
151 151 setitem / getitem (indexing):
152 152 wb.foo['key'] = 12
153 153 assert wb.foo['key'].v == 12
154 154
155 155 Note the asymmetry on setitem and getitem! Also other
156 156 dict methods are available.
157 157
158 158 .ipush() - run push-to-ipython
159 159
160 160 Minibuffer command access (tab completion works):
161 161
162 162 mb save-to-file
163 163
164 164 """
165 165 def __init__(self,p):
166 166 self.p = p.copy()
167 167
168 168 def __str__(self):
169 169 return "<LeoNode %s>" % str(self.p)
170 170
171 171 __repr__ = __str__
172 172
173 173 def __get_h(self): return self.p.headString()
174 174 def __set_h(self,val):
175 175 print "set head",val
176 176 c.beginUpdate()
177 177 try:
178 178 c.setHeadString(self.p,val)
179 179 finally:
180 180 c.endUpdate()
181 181
182 182 h = property( __get_h, __set_h, doc = "Node headline string")
183 183
184 184 def __get_b(self): return self.p.bodyString()
185 185 def __set_b(self,val):
186 186 print "set body",val
187 187 c.beginUpdate()
188 188 try:
189 189 c.setBodyString(self.p, val)
190 190 finally:
191 191 c.endUpdate()
192 192
193 193 b = property(__get_b, __set_b, doc = "Nody body string")
194 194
195 195 def __set_val(self, val):
196 196 self.b = format_for_leo(val)
197 197
198 198 v = property(lambda self: eval_node(self), __set_val, doc = "Node evaluated value")
199 199
200 200 def __set_l(self,val):
201 201 self.b = '\n'.join(val )
202 202 l = property(lambda self : IPython.genutils.SList(self.b.splitlines()),
203 203 __set_l, doc = "Node value as string list")
204 204
205 205 def __iter__(self):
206 206 """ Iterate through nodes direct children """
207 207
208 208 return (LeoNode(p) for p in self.p.children_iter())
209 209
210 210 def __children(self):
211 211 d = {}
212 212 for child in self:
213 213 head = child.h
214 214 tup = head.split(None,1)
215 215 if len(tup) > 1 and tup[0] == '@k':
216 216 d[tup[1]] = child
217 217 continue
218 218
219 219 if not valid_attribute(head):
220 220 d[head] = child
221 221 continue
222 222 return d
223 223 def keys(self):
224 224 d = self.__children()
225 225 return d.keys()
226 226 def __getitem__(self, key):
227 227 """ wb.foo['Some stuff'] Return a child node with headline 'Some stuff'
228 228
229 229 If key is a valid python name (e.g. 'foo'), look for headline '@k foo' as well
230 230 """
231 231 key = str(key)
232 232 d = self.__children()
233 233 return d[key]
234 234 def __setitem__(self, key, val):
235 235 """ You can do wb.foo['My Stuff'] = 12 to create children
236 236
237 237 This will create 'My Stuff' as a child of foo (if it does not exist), and
238 238 do .v = 12 assignment.
239 239
240 240 Exception:
241 241
242 242 wb.foo['bar'] = 12
243 243
244 244 will create a child with headline '@k bar', because bar is a valid python name
245 245 and we don't want to crowd the WorkBook namespace with (possibly numerous) entries
246 246 """
247 247 key = str(key)
248 248 d = self.__children()
249 249 if key in d:
250 250 d[key].v = val
251 251 return
252 252
253 253 if not valid_attribute(key):
254 254 head = key
255 255 else:
256 256 head = '@k ' + key
257 257 p = c.createLastChildNode(self.p, head, '')
258 258 LeoNode(p).v = val
259 259
260 260 def ipush(self):
261 261 """ Does push-to-ipython on the node """
262 262 push_from_leo(self)
263 263
264 264 def go(self):
265 265 """ Set node as current node (to quickly see it in Outline) """
266 266 c.beginUpdate()
267 267 try:
268 268 c.setCurrentPosition(self.p)
269 269 finally:
270 270 c.endUpdate()
271 271
272 272 def script(self):
273 273 """ Method to get the 'tangled' contents of the node
274 274
275 275 (parse @others, << section >> references etc.)
276 276 """
277 277 return g.getScript(c,self.p,useSelectedText=False,useSentinels=False)
278 278
279 279 def __get_uA(self):
280 280 p = self.p
281 281 # Create the uA if necessary.
282 282 if not hasattr(p.v.t,'unknownAttributes'):
283 283 p.v.t.unknownAttributes = {}
284 284
285 285 d = p.v.t.unknownAttributes.setdefault('ipython', {})
286 286 return d
287 287
288 288 uA = property(__get_uA, doc = "Access persistent unknownAttributes of node")
289 289
290 290
291 291 class LeoWorkbook:
292 292 """ class for 'advanced' node access
293 293
294 294 Has attributes for all "discoverable" nodes. Node is discoverable if it
295 295 either
296 296
297 297 - has a valid python name (Foo, bar_12)
298 298 - is a parent of an anchor node (if it has a child '@a foo', it is visible as foo)
299 299
300 300 """
301 301 def __getattr__(self, key):
302 302 if key.startswith('_') or key == 'trait_names' or not valid_attribute(key):
303 303 raise AttributeError
304 304 cells = all_cells()
305 305 p = cells.get(key, None)
306 306 if p is None:
307 307 return add_var(key)
308 308
309 309 return LeoNode(p)
310 310
311 311 def __str__(self):
312 312 return "<LeoWorkbook>"
313 313 def __setattr__(self,key, val):
314 314 raise AttributeError("Direct assignment to workbook denied, try wb.%s.v = %s" % (key,val))
315 315
316 316 __repr__ = __str__
317 317
318 318 def __iter__(self):
319 319 """ Iterate all (even non-exposed) nodes """
320 320 cells = all_cells()
321 321 return (LeoNode(p) for p in c.allNodes_iter())
322 322
323 323 current = property(lambda self: LeoNode(c.currentPosition()), doc = "Currently selected node")
324 324
325 325 def match_h(self, regex):
326 326 cmp = re.compile(regex)
327 327 for node in self:
328 328 if re.match(cmp, node.h, re.IGNORECASE):
329 329 yield node
330 330 return
331 331
332 332 @IPython.generics.complete_object.when_type(LeoWorkbook)
333 333 def workbook_complete(obj, prev):
334 334 return all_cells().keys() + [s for s in prev if not s.startswith('_')]
335 335
336 336
337 337 def add_var(varname):
338 338 c.beginUpdate()
339 339 r = rootnode()
340 340 try:
341 341 if r is None:
342 342 p2 = g.findNodeAnywhere(c,varname)
343 343 else:
344 344 p2 = g.findNodeInChildren(c, r.p, varname)
345 345 if p2:
346 346 return LeoNode(p2)
347 347
348 348 if r is not None:
349 349 p2 = r.p.insertAsLastChild()
350 350
351 351 else:
352 352 p2 = c.currentPosition().insertAfter()
353 353
354 354 c.setHeadString(p2,varname)
355 355 return LeoNode(p2)
356 356 finally:
357 357 c.endUpdate()
358 358
359 359 def add_file(self,fname):
360 360 p2 = c.currentPosition().insertAfter()
361 361
362 362 push_from_leo = CommandChainDispatcher()
363 363
364 364 def expose_ileo_push(f, prio = 0):
365 365 push_from_leo.add(f, prio)
366 366
367 367 def push_ipython_script(node):
368 368 """ Execute the node body in IPython, as if it was entered in interactive prompt """
369 369 c.beginUpdate()
370 370 try:
371 371 ohist = ip.IP.output_hist
372 372 hstart = len(ip.IP.input_hist)
373 373 script = node.script()
374 374
375 375 script = g.splitLines(script + '\n')
376 376 ip.user_ns['_p'] = node
377 377 ip.runlines(script)
378 del ip.user_ns['_p']
378 ip.user_ns.pop('_p',None)
379 379
380 380 has_output = False
381 381 for idx in range(hstart,len(ip.IP.input_hist)):
382 382 val = ohist.get(idx,None)
383 383 if val is None:
384 384 continue
385 385 has_output = True
386 386 inp = ip.IP.input_hist[idx]
387 387 if inp.strip():
388 388 es('In: %s' % (inp[:40], ))
389 389
390 390 es('<%d> %s' % (idx, pprint.pformat(ohist[idx],width = 40)))
391 391
392 392 if not has_output:
393 393 es('ipy run: %s (%d LL)' %( node.h,len(script)))
394 394 finally:
395 395 c.endUpdate()
396 396
397 397
398 398 def eval_body(body):
399 399 try:
400 400 val = ip.ev(body)
401 401 except:
402 402 # just use stringlist if it's not completely legal python expression
403 403 val = IPython.genutils.SList(body.splitlines())
404 404 return val
405 405
406 406 def push_plain_python(node):
407 407 if not node.h.endswith('P'):
408 408 raise TryNext
409 409 script = node.script()
410 410 lines = script.count('\n')
411 411 try:
412 412 exec script in ip.user_ns
413 413 except:
414 414 print " -- Exception in script:\n"+script + "\n --"
415 415 raise
416 416 es('ipy plain: %s (%d LL)' % (node.h,lines))
417 417
418 418
419 419 def push_cl_node(node):
420 420 """ If node starts with @cl, eval it
421 421
422 422 The result is put as last child of @ipy-results node, if it exists
423 423 """
424 424 if not node.b.startswith('@cl'):
425 425 raise TryNext
426 426
427 427 p2 = g.findNodeAnywhere(c,'@ipy-results')
428 428 val = node.v
429 429 if p2:
430 430 es("=> @ipy-results")
431 431 LeoNode(p2).v = val
432 432 es(val)
433 433
434 434 def push_ev_node(node):
435 435 """ If headline starts with @ev, eval it and put result in body """
436 436 if not node.h.startswith('@ev '):
437 437 raise TryNext
438 438 expr = node.h.lstrip('@ev ')
439 439 es('ipy eval ' + expr)
440 440 res = ip.ev(expr)
441 441 node.v = res
442 442
443 443
444 444 def push_position_from_leo(p):
445 445 push_from_leo(LeoNode(p))
446 446
447 447 @generic
448 448 def edit_object_in_leo(obj, varname):
449 449 """ Make it @cl node so it can be pushed back directly by alt+I """
450 450 node = add_var(varname)
451 451 formatted = format_for_leo(obj)
452 452 if not formatted.startswith('@cl'):
453 453 formatted = '@cl\n' + formatted
454 454 node.b = formatted
455 455 node.go()
456 456
457 457 @edit_object_in_leo.when_type(IPython.macro.Macro)
458 458 def edit_macro(obj,varname):
459 459 bod = '_ip.defmacro("""\\\n' + obj.value + '""")'
460 460 node = add_var('Macro_' + varname)
461 461 node.b = bod
462 462 node.go()
463 463
464 464 def get_history(hstart = 0):
465 465 res = []
466 466 ohist = ip.IP.output_hist
467 467
468 468 for idx in range(hstart, len(ip.IP.input_hist)):
469 469 val = ohist.get(idx,None)
470 470 has_output = True
471 471 inp = ip.IP.input_hist_raw[idx]
472 472 if inp.strip():
473 473 res.append('In [%d]: %s' % (idx, inp))
474 474 if val:
475 475 res.append(pprint.pformat(val))
476 476 res.append('\n')
477 477 return ''.join(res)
478 478
479 479
480 480 def lee_f(self,s):
481 481 """ Open file(s)/objects in Leo
482 482
483 483 - %lee hist -> open full session history in leo
484 484 - Takes an object
485 485 - Takes an mglob pattern, e.g. '%lee *.cpp' or %leo 'rec:*.cpp'
486 486 """
487 487 import os
488 488
489 489 c.beginUpdate()
490 490 try:
491 491 if s == 'hist':
492 492 wb.ipython_history.b = get_history()
493 493 wb.ipython_history.go()
494 494 return
495 495
496 496
497 497
498 498 # try editing the object directly
499 499 obj = ip.user_ns.get(s, None)
500 500 if obj is not None:
501 501 edit_object_in_leo(obj,s)
502 502 return
503 503
504 504 # if it's not object, it's a file name / mglob pattern
505 505 from IPython.external import mglob
506 506
507 507 files = (os.path.abspath(f) for f in mglob.expand(s))
508 508 for fname in files:
509 509 p = g.findNodeAnywhere(c,'@auto ' + fname)
510 510 if not p:
511 511 p = c.currentPosition().insertAfter()
512 512
513 513 p.setHeadString('@auto ' + fname)
514 514 if os.path.isfile(fname):
515 515 c.setBodyString(p,open(fname).read())
516 516 c.selectPosition(p)
517 517 print "Editing file(s), press ctrl+shift+w in Leo to write @auto nodes"
518 518 finally:
519 519 c.endUpdate()
520 520
521 521
522 522
523 523 def leoref_f(self,s):
524 524 """ Quick reference for ILeo """
525 525 import textwrap
526 526 print textwrap.dedent("""\
527 527 %leoe file/object - open file / object in leo
528 528 wb.foo.v - eval node foo (i.e. headstring is 'foo' or '@ipy foo')
529 529 wb.foo.v = 12 - assign to body of node foo
530 530 wb.foo.b - read or write the body of node foo
531 531 wb.foo.l - body of node foo as string list
532 532
533 533 for el in wb.foo:
534 534 print el.v
535 535
536 536 """
537 537 )
538 538
539 539
540 540
541 541 def mb_f(self, arg):
542 542 """ Execute leo minibuffer commands
543 543
544 544 Example:
545 545 mb save-to-file
546 546 """
547 547 c.executeMinibufferCommand(arg)
548 548
549 549 def mb_completer(self,event):
550 550 """ Custom completer for minibuffer """
551 551 cmd_param = event.line.split()
552 552 if event.line.endswith(' '):
553 553 cmd_param.append('')
554 554 if len(cmd_param) > 2:
555 555 return ip.IP.Completer.file_matches(event.symbol)
556 556 cmds = c.commandsDict.keys()
557 557 cmds.sort()
558 558 return cmds
559 559
560 560 def show_welcome():
561 561 print "------------------"
562 562 print "Welcome to Leo-enabled IPython session!"
563 563 print "Try %leoref for quick reference."
564 564 import IPython.platutils
565 565 IPython.platutils.set_term_title('ILeo')
566 566 IPython.platutils.freeze_term_title()
567 567
568 568 def run_leo_startup_node():
569 569 p = g.findNodeAnywhere(c,'@ipy-startup')
570 570 if p:
571 571 print "Running @ipy-startup nodes"
572 572 for n in LeoNode(p):
573 573 push_from_leo(n)
574 574
575 575
General Comments 0
You need to be logged in to leave comments. Login now