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