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