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