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