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