##// END OF EJS Templates
merge from ileo-exp. Now requires trunk version of leo
Ville M. Vainio -
r1031:11c30a1d merge
parent child Browse files
Show More
@@ -9,15 +9,52 b' from IPython.hooks import CommandChainDispatcher'
9 9 import re
10 10 import UserDict
11 11 from IPython.ipapi import TryNext
12 import IPython.macro
12 13
13 ip = IPython.ipapi.get()
14 leo = ip.user_ns['leox']
15 c,g = leo.c, leo.g
14 def init_ipython(ipy):
15 """ This will be run by _ip.load('ipy_leo')
16
17 Leo still needs to run update_commander() after this.
18
19 """
20 global ip
21 ip = ipy
22 ip.set_hook('complete_command', mb_completer, str_key = '%mb')
23 ip.expose_magic('mb',mb_f)
24 ip.expose_magic('lee',lee_f)
25 ip.expose_magic('leoref',leoref_f)
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
28 expose_ileo_push(push_ipython_script, 1000)
29 expose_ileo_push(push_plain_python, 100)
30 expose_ileo_push(push_ev_node, 100)
31 global wb
32 wb = LeoWorkbook()
33 ip.user_ns['wb'] = wb
34
35 show_welcome()
16 36
17 # will probably be overwritten by user, but handy for experimentation early on
18 ip.user_ns['c'] = c
19 ip.user_ns['g'] = g
20 37
38 def update_commander(new_leox):
39 """ Set the Leo commander to use
40
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
43 ipython-launch to tell ILeo what document the commands apply to.
44
45 """
46
47 global c,g
48 c,g = new_leox.c, new_leox.g
49 print "Set Leo Commander:",c.frame.getTitle()
50
51 # will probably be overwritten by user, but handy for experimentation early on
52 ip.user_ns['c'] = c
53 ip.user_ns['g'] = g
54 ip.user_ns['_leo'] = new_leox
55
56 new_leox.push = push_position_from_leo
57 run_leo_startup_node()
21 58
22 59 from IPython.external.simplegeneric import generic
23 60 import pprint
@@ -34,23 +71,51 b' def format_for_leo(obj):'
34 71 @format_for_leo.when_type(list)
35 72 def format_list(obj):
36 73 return "\n".join(str(s) for s in obj)
74
37 75
38 76 attribute_re = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
39 77 def valid_attribute(s):
40 78 return attribute_re.match(s)
41 79
80 _rootnode = None
81 def rootnode():
82 """ Get ileo root node (@ipy-root)
83
84 if node has become invalid or has not been set, return None
85
86 Note that the root is the *first* @ipy-root item found
87 """
88 global _rootnode
89 if _rootnode is None:
90 return None
91 if c.positionExists(_rootnode.p):
92 return _rootnode
93 _rootnode = None
94 return None
95
42 96 def all_cells():
97 global _rootnode
43 98 d = {}
44 for p in c.allNodes_iter():
99 r = rootnode()
100 if r is not None:
101 nodes = r.p.children_iter()
102 else:
103 nodes = c.allNodes_iter()
104
105 for p in nodes:
45 106 h = p.headString()
107 if h.strip() == '@ipy-root':
108 # update root node (found it for the first time)
109 _rootnode = LeoNode(p)
110 # the next recursive call will use the children of new root
111 return all_cells()
112
46 113 if h.startswith('@a '):
47 114 d[h.lstrip('@a ').strip()] = p.parent().copy()
48 115 elif not valid_attribute(h):
49 116 continue
50 117 d[h] = p.copy()
51 118 return d
52
53
54 119
55 120 def eval_node(n):
56 121 body = n.b
@@ -91,6 +156,10 b' class LeoNode(object, UserDict.DictMixin):'
91 156 dict methods are available.
92 157
93 158 .ipush() - run push-to-ipython
159
160 Minibuffer command access (tab completion works):
161
162 mb save-to-file
94 163
95 164 """
96 165 def __init__(self,p):
@@ -235,7 +304,7 b' class LeoWorkbook:'
235 304 cells = all_cells()
236 305 p = cells.get(key, None)
237 306 if p is None:
238 p = add_var(key)
307 return add_var(key)
239 308
240 309 return LeoNode(p)
241 310
@@ -259,10 +328,6 b' class LeoWorkbook:'
259 328 if re.match(cmp, node.h, re.IGNORECASE):
260 329 yield node
261 330 return
262
263 ip.user_ns['wb'] = LeoWorkbook()
264
265
266 331
267 332 @IPython.generics.complete_object.when_type(LeoWorkbook)
268 333 def workbook_complete(obj, prev):
@@ -271,17 +336,23 b' def workbook_complete(obj, prev):'
271 336
272 337 def add_var(varname):
273 338 c.beginUpdate()
339 r = rootnode()
274 340 try:
275 p2 = g.findNodeAnywhere(c,varname)
341 if r is None:
342 p2 = g.findNodeAnywhere(c,varname)
343 else:
344 p2 = g.findNodeInChildren(c, r.p, varname)
276 345 if p2:
277 return
346 return LeoNode(p2)
278 347
279 rootpos = g.findNodeAnywhere(c,'@ipy-results')
280 if not rootpos:
281 rootpos = c.currentPosition()
282 p2 = rootpos.insertAsLastChild()
348 if r is not None:
349 p2 = r.p.insertAsLastChild()
350
351 else:
352 p2 = c.currentPosition().insertAfter()
353
283 354 c.setHeadString(p2,varname)
284 return p2
355 return LeoNode(p2)
285 356 finally:
286 357 c.endUpdate()
287 358
@@ -302,8 +373,9 b' def push_ipython_script(node):'
302 373 script = node.script()
303 374
304 375 script = g.splitLines(script + '\n')
305
376 ip.user_ns['_p'] = node
306 377 ip.runlines(script)
378 ip.user_ns.pop('_p',None)
307 379
308 380 has_output = False
309 381 for idx in range(hstart,len(ip.IP.input_hist)):
@@ -322,8 +394,6 b' def push_ipython_script(node):'
322 394 finally:
323 395 c.endUpdate()
324 396
325 # this should be the LAST one that will be executed, and it will never raise TryNext
326 expose_ileo_push(push_ipython_script, 1000)
327 397
328 398 def eval_body(body):
329 399 try:
@@ -345,12 +415,11 b' def push_plain_python(node):'
345 415 raise
346 416 es('ipy plain: %s (%d LL)' % (node.h,lines))
347 417
348 expose_ileo_push(push_plain_python, 100)
349 418
350 419 def push_cl_node(node):
351 420 """ If node starts with @cl, eval it
352 421
353 The result is put to root @ipy-results node
422 The result is put as last child of @ipy-results node, if it exists
354 423 """
355 424 if not node.b.startswith('@cl'):
356 425 raise TryNext
@@ -362,24 +431,80 b' def push_cl_node(node):'
362 431 LeoNode(p2).v = val
363 432 es(val)
364 433
365 expose_ileo_push(push_cl_node,100)
366
434 def push_ev_node(node):
435 """ If headline starts with @ev, eval it and put result in body """
436 if not node.h.startswith('@ev '):
437 raise TryNext
438 expr = node.h.lstrip('@ev ')
439 es('ipy eval ' + expr)
440 res = ip.ev(expr)
441 node.v = res
442
443
367 444 def push_position_from_leo(p):
368 push_from_leo(LeoNode(p))
445 push_from_leo(LeoNode(p))
446
447 @generic
448 def edit_object_in_leo(obj, varname):
449 """ Make it @cl node so it can be pushed back directly by alt+I """
450 node = add_var(varname)
451 formatted = format_for_leo(obj)
452 if not formatted.startswith('@cl'):
453 formatted = '@cl\n' + formatted
454 node.b = formatted
455 node.go()
456
457 @edit_object_in_leo.when_type(IPython.macro.Macro)
458 def edit_macro(obj,varname):
459 bod = '_ip.defmacro("""\\\n' + obj.value + '""")'
460 node = add_var('Macro_' + varname)
461 node.b = bod
462 node.go()
463
464 def get_history(hstart = 0):
465 res = []
466 ohist = ip.IP.output_hist
467
468 for idx in range(hstart, len(ip.IP.input_hist)):
469 val = ohist.get(idx,None)
470 has_output = True
471 inp = ip.IP.input_hist_raw[idx]
472 if inp.strip():
473 res.append('In [%d]: %s' % (idx, inp))
474 if val:
475 res.append(pprint.pformat(val))
476 res.append('\n')
477 return ''.join(res)
369 478
370 ip.user_ns['leox'].push = push_position_from_leo
371 479
372 def leo_f(self,s):
373 """ open file(s) in Leo
480 def lee_f(self,s):
481 """ Open file(s)/objects in Leo
374 482
375 Takes an mglob pattern, e.g. '%leo *.cpp' or %leo 'rec:*.cpp'
483 - %lee hist -> open full session history in leo
484 - Takes an object
485 - Takes an mglob pattern, e.g. '%lee *.cpp' or %leo 'rec:*.cpp'
376 486 """
377 487 import os
378 from IPython.external import mglob
379 488
380 files = mglob.expand(s)
381 489 c.beginUpdate()
382 490 try:
491 if s == 'hist':
492 wb.ipython_history.b = get_history()
493 wb.ipython_history.go()
494 return
495
496
497
498 # try editing the object directly
499 obj = ip.user_ns.get(s, None)
500 if obj is not None:
501 edit_object_in_leo(obj,s)
502 return
503
504 # if it's not object, it's a file name / mglob pattern
505 from IPython.external import mglob
506
507 files = (os.path.abspath(f) for f in mglob.expand(s))
383 508 for fname in files:
384 509 p = g.findNodeAnywhere(c,'@auto ' + fname)
385 510 if not p:
@@ -389,16 +514,17 b' def leo_f(self,s):'
389 514 if os.path.isfile(fname):
390 515 c.setBodyString(p,open(fname).read())
391 516 c.selectPosition(p)
517 print "Editing file(s), press ctrl+shift+w in Leo to write @auto nodes"
392 518 finally:
393 519 c.endUpdate()
394 520
395 ip.expose_magic('leo',leo_f)
521
396 522
397 523 def leoref_f(self,s):
398 524 """ Quick reference for ILeo """
399 525 import textwrap
400 526 print textwrap.dedent("""\
401 %leo file - open file in leo
527 %leoe file/object - open file / object in leo
402 528 wb.foo.v - eval node foo (i.e. headstring is 'foo' or '@ipy foo')
403 529 wb.foo.v = 12 - assign to body of node foo
404 530 wb.foo.b - read or write the body of node foo
@@ -409,14 +535,15 b' def leoref_f(self,s):'
409 535
410 536 """
411 537 )
412 ip.expose_magic('leoref',leoref_f)
413 538
414 from ipy_leo import *
415 539
416 ip = IPython.ipapi.get()
417 540
418 541 def mb_f(self, arg):
419 """ Execute leo minibuffer commands """
542 """ Execute leo minibuffer commands
543
544 Example:
545 mb save-to-file
546 """
420 547 c.executeMinibufferCommand(arg)
421 548
422 549 def mb_completer(self,event):
@@ -430,11 +557,6 b' def mb_completer(self,event):'
430 557 cmds.sort()
431 558 return cmds
432 559
433 pass
434 ip.set_hook('complete_command', mb_completer, str_key = 'mb')
435 ip.expose_magic('mb',mb_f)
436
437
438 560 def show_welcome():
439 561 print "------------------"
440 562 print "Welcome to Leo-enabled IPython session!"
@@ -449,7 +571,5 b' def run_leo_startup_node():'
449 571 print "Running @ipy-startup nodes"
450 572 for n in LeoNode(p):
451 573 push_from_leo(n)
452
453 run_leo_startup_node()
454 show_welcome()
574
455 575
@@ -41,8 +41,9 b' Now launch a new IPython prompt and kill the process:'
41 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 *
45 45 import os,shlex,sys,time
46 import threading,Queue
46 47
47 48 from IPython import genutils
48 49
@@ -71,15 +72,70 b' def startjob(job):'
71 72 p.line = job
72 73 return p
73 74
75 class AsyncJobQ(threading.Thread):
76 def __init__(self):
77 threading.Thread.__init__(self)
78 self.q = Queue.Queue()
79 self.output = []
80 self.stop = False
81 def run(self):
82 while 1:
83 cmd,cwd = self.q.get()
84 if self.stop:
85 self.output.append("** Discarding: '%s' - %s" % (cmd,cwd))
86 continue
87 self.output.append("** Task started: '%s' - %s" % (cmd,cwd))
88
89 p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd = cwd)
90 out = p.stdout.read()
91 self.output.append("** Task complete: '%s'\n" % cmd)
92 self.output.append(out)
93
94 def add(self,cmd):
95 self.q.put_nowait((cmd, os.getcwd()))
96
97 def dumpoutput(self):
98 while self.output:
99 item = self.output.pop(0)
100 print item
101
102 _jobq = None
103
104 def jobqueue_f(self, line):
105
106 global _jobq
107 if not _jobq:
108 print "Starting jobqueue - do '&some_long_lasting_system_command' to enqueue"
109 _jobq = AsyncJobQ()
110 _jobq.setDaemon(True)
111 _jobq.start()
112 ip.jobq = _jobq.add
113 return
114 if line.strip() == 'stop':
115 print "Stopping and clearing jobqueue, %jobqueue start to start again"
116 _jobq.stop = True
117 return
118 if line.strip() == 'start':
119 _jobq.stop = False
120 return
121
74 122 def jobctrl_prefilter_f(self,line):
75 123 if line.startswith('&'):
76 124 pre,fn,rest = self.split_user_input(line[1:])
77 125
78 126 line = ip.IP.expand_aliases(fn,rest)
79 return '_ip.startjob(%s)' % genutils.make_quoted_expr(line)
127 if not _jobq:
128 return '_ip.startjob(%s)' % genutils.make_quoted_expr(line)
129 return '_ip.jobq(%s)' % genutils.make_quoted_expr(line)
80 130
81 131 raise IPython.ipapi.TryNext
82 132
133 def jobq_output_hook(self):
134 if not _jobq:
135 return
136 _jobq.dumpoutput()
137
138
83 139
84 140 def job_list(ip):
85 141 keys = ip.db.keys('tasks/*')
@@ -91,8 +147,16 b' def magic_tasks(self,line):'
91 147
92 148 A 'task' is a process that has been started in IPython when 'jobctrl' extension is enabled.
93 149 Tasks can be killed with %kill.
150
151 '%tasks clear' clears the task list (from stale tasks)
94 152 """
95 153 ip = self.getapi()
154 if line.strip() == 'clear':
155 for k in ip.db.keys('tasks/*'):
156 print "Clearing",ip.db[k]
157 del ip.db[k]
158 return
159
96 160 ents = job_list(ip)
97 161 if not ents:
98 162 print "No tasks running"
@@ -133,20 +197,33 b' else:'
133 197
134 198 def jobctrl_shellcmd(ip,cmd):
135 199 """ os.system replacement that stores process info to db['tasks/t1234'] """
200 cmd = cmd.strip()
136 201 cmdname = cmd.split(None,1)[0]
137 202 if cmdname in shell_internal_commands:
138 203 use_shell = True
139 204 else:
140 205 use_shell = False
141 206
142 p = Popen(cmd,shell = use_shell)
143 jobentry = 'tasks/t' + str(p.pid)
144
207 jobentry = None
145 208 try:
209 try:
210 p = Popen(cmd,shell = use_shell)
211 except WindowsError:
212 if use_shell:
213 # try with os.system
214 os.system(cmd)
215 return
216 else:
217 # have to go via shell, sucks
218 p = Popen(cmd,shell = True)
219
220 jobentry = 'tasks/t' + str(p.pid)
146 221 ip.db[jobentry] = (p.pid,cmd,os.getcwd(),time.time())
147 p.communicate()
222 p.communicate()
223
148 224 finally:
149 del ip.db[jobentry]
225 if jobentry:
226 del ip.db[jobentry]
150 227
151 228
152 229 def install():
@@ -158,5 +235,6 b' def install():'
158 235 ip.set_hook('shell_hook', jobctrl_shellcmd)
159 236 ip.expose_magic('kill',magic_kill)
160 237 ip.expose_magic('tasks',magic_tasks)
161
238 ip.expose_magic('jobqueue',jobqueue_f)
239 ip.set_hook('pre_prompt_hook', jobq_output_hook)
162 240 install()
@@ -2670,6 +2670,7 b' Defaulting color scheme to \'NoColor\'"""'
2670 2670 parameter_s = parameter_s.strip()
2671 2671 #bkms = self.shell.persist.get("bookmarks",{})
2672 2672
2673 oldcwd = os.getcwd()
2673 2674 numcd = re.match(r'(-)(\d+)$',parameter_s)
2674 2675 # jump in directory history by number
2675 2676 if numcd:
@@ -2708,7 +2709,7 b' Defaulting color scheme to \'NoColor\'"""'
2708 2709
2709 2710 # at this point ps should point to the target dir
2710 2711 if ps:
2711 try:
2712 try:
2712 2713 os.chdir(os.path.expanduser(ps))
2713 2714 if self.shell.rc.term_title:
2714 2715 #print 'set term title:',self.shell.rc.term_title # dbg
@@ -2719,8 +2720,9 b' Defaulting color scheme to \'NoColor\'"""'
2719 2720 else:
2720 2721 cwd = os.getcwd()
2721 2722 dhist = self.shell.user_ns['_dh']
2722 dhist.append(cwd)
2723 self.db['dhist'] = compress_dhist(dhist)[-100:]
2723 if oldcwd != cwd:
2724 dhist.append(cwd)
2725 self.db['dhist'] = compress_dhist(dhist)[-100:]
2724 2726
2725 2727 else:
2726 2728 os.chdir(self.shell.home_dir)
@@ -2728,8 +2730,10 b' Defaulting color scheme to \'NoColor\'"""'
2728 2730 platutils.set_term_title("IPy ~")
2729 2731 cwd = os.getcwd()
2730 2732 dhist = self.shell.user_ns['_dh']
2731 dhist.append(cwd)
2732 self.db['dhist'] = compress_dhist(dhist)[-100:]
2733
2734 if oldcwd != cwd:
2735 dhist.append(cwd)
2736 self.db['dhist'] = compress_dhist(dhist)[-100:]
2733 2737 if not 'q' in opts and self.shell.user_ns['_dh']:
2734 2738 print self.shell.user_ns['_dh'][-1]
2735 2739
@@ -56,7 +56,7 b' from pprint import PrettyPrinter'
56 56 __all__ = ['editor', 'fix_error_editor', 'result_display',
57 57 'input_prefilter', 'shutdown_hook', 'late_startup_hook',
58 58 'generate_prompt', 'generate_output_prompt','shell_hook',
59 'show_in_pager']
59 'show_in_pager','pre_prompt_hook']
60 60
61 61 pformat = PrettyPrinter().pformat
62 62
@@ -227,8 +227,13 b' def show_in_pager(self,s):'
227 227 # raising TryNext here will use the default paging functionality
228 228 raise ipapi.TryNext
229 229
230 def pre_command_hook(self,cmd):
231 """" Executed before starting to execute a command """
230 def pre_prompt_hook(self):
231 """ Run before displaying the next prompt
232
233 Use this e.g. to display output from asynchronous operations (in order
234 to not mess up text entry)
235 """
236
232 237 return None
233 238
234 239 def post_command_hook(self,cmd):
@@ -6,7 +6,6 b' Requires Python 2.3 or newer.'
6 6
7 7 This file contains all the classes and helper functions specific to IPython.
8 8
9 $Id: iplib.py 3005 2008-02-01 16:43:34Z vivainio $
10 9 """
11 10
12 11 #*****************************************************************************
@@ -377,7 +376,10 b' class InteractiveShell(object,Magic):'
377 376 # Get system encoding at startup time. Certain terminals (like Emacs
378 377 # under Win32 have it set to None, and we need to have a known valid
379 378 # encoding to use in the raw_input() method
380 self.stdin_encoding = sys.stdin.encoding or 'ascii'
379 try:
380 self.stdin_encoding = sys.stdin.encoding or 'ascii'
381 except AttributeError:
382 self.stdin_encoding = 'ascii'
381 383
382 384 # dict of things NOT to alias (keywords, builtins and some magics)
383 385 no_alias = {}
@@ -1744,6 +1746,7 b' want to merge them back into the new files.""" % locals()'
1744 1746 # exit_now is set by a call to %Exit or %Quit
1745 1747
1746 1748 while not self.exit_now:
1749 self.hooks.pre_prompt_hook()
1747 1750 if more:
1748 1751 try:
1749 1752 prompt = self.hooks.generate_prompt(True)
General Comments 0
You need to be logged in to leave comments. Login now