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