##// END OF EJS Templates
cd -foo jumps to dir matching 'foo' in directory history
Ville M. Vainio -
Show More

The requested changes are too big and content was truncated. Show full diff

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