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