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