##// END OF EJS Templates
Prompt messages are now implemented for both in/out prompts.
Brian Granger -
Show More
@@ -1,289 +1,288 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Displayhook for IPython.
2 """Displayhook for IPython.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian Granger
7 * Brian Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2010 The IPython Development Team
11 # Copyright (C) 2008-2010 The IPython Development Team
12 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
12 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import __builtin__
22 import __builtin__
23 from pprint import PrettyPrinter
23 from pprint import PrettyPrinter
24 pformat = PrettyPrinter().pformat
24 pformat = PrettyPrinter().pformat
25
25
26 from IPython.config.configurable import Configurable
26 from IPython.config.configurable import Configurable
27 from IPython.core import prompts
27 from IPython.core import prompts
28 import IPython.utils.generics
28 import IPython.utils.generics
29 import IPython.utils.io
29 import IPython.utils.io
30 from IPython.utils.traitlets import Instance
30 from IPython.utils.traitlets import Instance, Int
31 from IPython.utils.warn import warn
31 from IPython.utils.warn import warn
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Main displayhook class
34 # Main displayhook class
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 # TODO: The DisplayHook class should be split into two classes, one that
37 # TODO: The DisplayHook class should be split into two classes, one that
38 # manages the prompts and their synchronization and another that just does the
38 # manages the prompts and their synchronization and another that just does the
39 # displayhook logic and calls into the prompt manager.
39 # displayhook logic and calls into the prompt manager.
40
40
41 # TODO: Move the various attributes (cache_size, colors, input_sep,
41 # TODO: Move the various attributes (cache_size, colors, input_sep,
42 # output_sep, output_sep2, ps1, ps2, ps_out, pad_left). Some of these are also
42 # output_sep, output_sep2, ps1, ps2, ps_out, pad_left). Some of these are also
43 # attributes of InteractiveShell. They should be on ONE object only and the
43 # attributes of InteractiveShell. They should be on ONE object only and the
44 # other objects should ask that one object for their values.
44 # other objects should ask that one object for their values.
45
45
46 class DisplayHook(Configurable):
46 class DisplayHook(Configurable):
47 """The custom IPython displayhook to replace sys.displayhook.
47 """The custom IPython displayhook to replace sys.displayhook.
48
48
49 This class does many things, but the basic idea is that it is a callable
49 This class does many things, but the basic idea is that it is a callable
50 that gets called anytime user code returns a value.
50 that gets called anytime user code returns a value.
51
51
52 Currently this class does more than just the displayhook logic and that
52 Currently this class does more than just the displayhook logic and that
53 extra logic should eventually be moved out of here.
53 extra logic should eventually be moved out of here.
54 """
54 """
55
55
56 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
56 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
57 # Each call to the In[] prompt raises it by 1, even the first.
58 prompt_count = Int(0)
57
59
58 def __init__(self, shell=None, cache_size=1000,
60 def __init__(self, shell=None, cache_size=1000,
59 colors='NoColor', input_sep='\n',
61 colors='NoColor', input_sep='\n',
60 output_sep='\n', output_sep2='',
62 output_sep='\n', output_sep2='',
61 ps1 = None, ps2 = None, ps_out = None, pad_left=True,
63 ps1 = None, ps2 = None, ps_out = None, pad_left=True,
62 config=None):
64 config=None):
63 super(DisplayHook, self).__init__(shell=shell, config=config)
65 super(DisplayHook, self).__init__(shell=shell, config=config)
64
66
65 cache_size_min = 3
67 cache_size_min = 3
66 if cache_size <= 0:
68 if cache_size <= 0:
67 self.do_full_cache = 0
69 self.do_full_cache = 0
68 cache_size = 0
70 cache_size = 0
69 elif cache_size < cache_size_min:
71 elif cache_size < cache_size_min:
70 self.do_full_cache = 0
72 self.do_full_cache = 0
71 cache_size = 0
73 cache_size = 0
72 warn('caching was disabled (min value for cache size is %s).' %
74 warn('caching was disabled (min value for cache size is %s).' %
73 cache_size_min,level=3)
75 cache_size_min,level=3)
74 else:
76 else:
75 self.do_full_cache = 1
77 self.do_full_cache = 1
76
78
77 self.cache_size = cache_size
79 self.cache_size = cache_size
78 self.input_sep = input_sep
80 self.input_sep = input_sep
79
81
80 # we need a reference to the user-level namespace
82 # we need a reference to the user-level namespace
81 self.shell = shell
83 self.shell = shell
82
84
83 # Set input prompt strings and colors
85 # Set input prompt strings and colors
84 if cache_size == 0:
86 if cache_size == 0:
85 if ps1.find('%n') > -1 or ps1.find(r'\#') > -1 \
87 if ps1.find('%n') > -1 or ps1.find(r'\#') > -1 \
86 or ps1.find(r'\N') > -1:
88 or ps1.find(r'\N') > -1:
87 ps1 = '>>> '
89 ps1 = '>>> '
88 if ps2.find('%n') > -1 or ps2.find(r'\#') > -1 \
90 if ps2.find('%n') > -1 or ps2.find(r'\#') > -1 \
89 or ps2.find(r'\N') > -1:
91 or ps2.find(r'\N') > -1:
90 ps2 = '... '
92 ps2 = '... '
91 self.ps1_str = self._set_prompt_str(ps1,'In [\\#]: ','>>> ')
93 self.ps1_str = self._set_prompt_str(ps1,'In [\\#]: ','>>> ')
92 self.ps2_str = self._set_prompt_str(ps2,' .\\D.: ','... ')
94 self.ps2_str = self._set_prompt_str(ps2,' .\\D.: ','... ')
93 self.ps_out_str = self._set_prompt_str(ps_out,'Out[\\#]: ','')
95 self.ps_out_str = self._set_prompt_str(ps_out,'Out[\\#]: ','')
94
96
95 self.color_table = prompts.PromptColors
97 self.color_table = prompts.PromptColors
96 self.prompt1 = prompts.Prompt1(self,sep=input_sep,prompt=self.ps1_str,
98 self.prompt1 = prompts.Prompt1(self,sep=input_sep,prompt=self.ps1_str,
97 pad_left=pad_left)
99 pad_left=pad_left)
98 self.prompt2 = prompts.Prompt2(self,prompt=self.ps2_str,pad_left=pad_left)
100 self.prompt2 = prompts.Prompt2(self,prompt=self.ps2_str,pad_left=pad_left)
99 self.prompt_out = prompts.PromptOut(self,sep='',prompt=self.ps_out_str,
101 self.prompt_out = prompts.PromptOut(self,sep='',prompt=self.ps_out_str,
100 pad_left=pad_left)
102 pad_left=pad_left)
101 self.set_colors(colors)
103 self.set_colors(colors)
102
104
103 # other more normal stuff
104 # b/c each call to the In[] prompt raises it by 1, even the first.
105 self.prompt_count = 0
106 # Store the last prompt string each time, we need it for aligning
105 # Store the last prompt string each time, we need it for aligning
107 # continuation and auto-rewrite prompts
106 # continuation and auto-rewrite prompts
108 self.last_prompt = ''
107 self.last_prompt = ''
109 self.output_sep = output_sep
108 self.output_sep = output_sep
110 self.output_sep2 = output_sep2
109 self.output_sep2 = output_sep2
111 self._,self.__,self.___ = '','',''
110 self._,self.__,self.___ = '','',''
112 self.pprint_types = map(type,[(),[],{}])
111 self.pprint_types = map(type,[(),[],{}])
113
112
114 # these are deliberately global:
113 # these are deliberately global:
115 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
114 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
116 self.shell.user_ns.update(to_user_ns)
115 self.shell.user_ns.update(to_user_ns)
117
116
118 def _set_prompt_str(self,p_str,cache_def,no_cache_def):
117 def _set_prompt_str(self,p_str,cache_def,no_cache_def):
119 if p_str is None:
118 if p_str is None:
120 if self.do_full_cache:
119 if self.do_full_cache:
121 return cache_def
120 return cache_def
122 else:
121 else:
123 return no_cache_def
122 return no_cache_def
124 else:
123 else:
125 return p_str
124 return p_str
126
125
127 def set_colors(self, colors):
126 def set_colors(self, colors):
128 """Set the active color scheme and configure colors for the three
127 """Set the active color scheme and configure colors for the three
129 prompt subsystems."""
128 prompt subsystems."""
130
129
131 # FIXME: This modifying of the global prompts.prompt_specials needs
130 # FIXME: This modifying of the global prompts.prompt_specials needs
132 # to be fixed. We need to refactor all of the prompts stuff to use
131 # to be fixed. We need to refactor all of the prompts stuff to use
133 # proper configuration and traits notifications.
132 # proper configuration and traits notifications.
134 if colors.lower()=='nocolor':
133 if colors.lower()=='nocolor':
135 prompts.prompt_specials = prompts.prompt_specials_nocolor
134 prompts.prompt_specials = prompts.prompt_specials_nocolor
136 else:
135 else:
137 prompts.prompt_specials = prompts.prompt_specials_color
136 prompts.prompt_specials = prompts.prompt_specials_color
138
137
139 self.color_table.set_active_scheme(colors)
138 self.color_table.set_active_scheme(colors)
140 self.prompt1.set_colors()
139 self.prompt1.set_colors()
141 self.prompt2.set_colors()
140 self.prompt2.set_colors()
142 self.prompt_out.set_colors()
141 self.prompt_out.set_colors()
143
142
144 #-------------------------------------------------------------------------
143 #-------------------------------------------------------------------------
145 # Methods used in __call__. Override these methods to modify the behavior
144 # Methods used in __call__. Override these methods to modify the behavior
146 # of the displayhook.
145 # of the displayhook.
147 #-------------------------------------------------------------------------
146 #-------------------------------------------------------------------------
148
147
149 def check_for_underscore(self):
148 def check_for_underscore(self):
150 """Check if the user has set the '_' variable by hand."""
149 """Check if the user has set the '_' variable by hand."""
151 # If something injected a '_' variable in __builtin__, delete
150 # If something injected a '_' variable in __builtin__, delete
152 # ipython's automatic one so we don't clobber that. gettext() in
151 # ipython's automatic one so we don't clobber that. gettext() in
153 # particular uses _, so we need to stay away from it.
152 # particular uses _, so we need to stay away from it.
154 if '_' in __builtin__.__dict__:
153 if '_' in __builtin__.__dict__:
155 try:
154 try:
156 del self.shell.user_ns['_']
155 del self.shell.user_ns['_']
157 except KeyError:
156 except KeyError:
158 pass
157 pass
159
158
160 def quiet(self):
159 def quiet(self):
161 """Should we silence the display hook because of ';'?"""
160 """Should we silence the display hook because of ';'?"""
162 # do not print output if input ends in ';'
161 # do not print output if input ends in ';'
163 try:
162 try:
164 if self.shell.input_hist[self.prompt_count].endswith(';\n'):
163 if self.shell.input_hist[self.prompt_count].endswith(';\n'):
165 return True
164 return True
166 except IndexError:
165 except IndexError:
167 # some uses of ipshellembed may fail here
166 # some uses of ipshellembed may fail here
168 pass
167 pass
169 return False
168 return False
170
169
171 def start_displayhook(self):
170 def start_displayhook(self):
172 """Start the displayhook, initializing resources."""
171 """Start the displayhook, initializing resources."""
173 pass
172 pass
174
173
175 def write_output_prompt(self):
174 def write_output_prompt(self):
176 """Write the output prompt."""
175 """Write the output prompt."""
177 # Use write, not print which adds an extra space.
176 # Use write, not print which adds an extra space.
178 IPython.utils.io.Term.cout.write(self.output_sep)
177 IPython.utils.io.Term.cout.write(self.output_sep)
179 outprompt = str(self.prompt_out)
178 outprompt = str(self.prompt_out)
180 if self.do_full_cache:
179 if self.do_full_cache:
181 IPython.utils.io.Term.cout.write(outprompt)
180 IPython.utils.io.Term.cout.write(outprompt)
182
181
183 # TODO: Make this method an extension point. The previous implementation
182 # TODO: Make this method an extension point. The previous implementation
184 # has both a result_display hook as well as a result_display generic
183 # has both a result_display hook as well as a result_display generic
185 # function to customize the repr on a per class basis. We need to rethink
184 # function to customize the repr on a per class basis. We need to rethink
186 # the hooks mechanism before doing this though.
185 # the hooks mechanism before doing this though.
187 def compute_result_repr(self, result):
186 def compute_result_repr(self, result):
188 """Compute and return the repr of the object to be displayed.
187 """Compute and return the repr of the object to be displayed.
189
188
190 This method only compute the string form of the repr and should NOT
189 This method only compute the string form of the repr and should NOT
191 actual print or write that to a stream. This method may also transform
190 actual print or write that to a stream. This method may also transform
192 the result itself, but the default implementation passes the original
191 the result itself, but the default implementation passes the original
193 through.
192 through.
194 """
193 """
195 try:
194 try:
196 if self.shell.pprint:
195 if self.shell.pprint:
197 result_repr = pformat(result)
196 result_repr = pformat(result)
198 if '\n' in result_repr:
197 if '\n' in result_repr:
199 # So that multi-line strings line up with the left column of
198 # So that multi-line strings line up with the left column of
200 # the screen, instead of having the output prompt mess up
199 # the screen, instead of having the output prompt mess up
201 # their first line.
200 # their first line.
202 result_repr = '\n' + result_repr
201 result_repr = '\n' + result_repr
203 else:
202 else:
204 result_repr = repr(result)
203 result_repr = repr(result)
205 except TypeError:
204 except TypeError:
206 # This happens when result.__repr__ doesn't return a string,
205 # This happens when result.__repr__ doesn't return a string,
207 # such as when it returns None.
206 # such as when it returns None.
208 result_repr = '\n'
207 result_repr = '\n'
209 return result, result_repr
208 return result, result_repr
210
209
211 def write_result_repr(self, result_repr):
210 def write_result_repr(self, result_repr):
212 # We want to print because we want to always make sure we have a
211 # We want to print because we want to always make sure we have a
213 # newline, even if all the prompt separators are ''. This is the
212 # newline, even if all the prompt separators are ''. This is the
214 # standard IPython behavior.
213 # standard IPython behavior.
215 print >>IPython.utils.io.Term.cout, result_repr
214 print >>IPython.utils.io.Term.cout, result_repr
216
215
217 def update_user_ns(self, result):
216 def update_user_ns(self, result):
218 """Update user_ns with various things like _, __, _1, etc."""
217 """Update user_ns with various things like _, __, _1, etc."""
219
218
220 # Avoid recursive reference when displaying _oh/Out
219 # Avoid recursive reference when displaying _oh/Out
221 if result is not self.shell.user_ns['_oh']:
220 if result is not self.shell.user_ns['_oh']:
222 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
221 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
223 warn('Output cache limit (currently '+
222 warn('Output cache limit (currently '+
224 `self.cache_size`+' entries) hit.\n'
223 `self.cache_size`+' entries) hit.\n'
225 'Flushing cache and resetting history counter...\n'
224 'Flushing cache and resetting history counter...\n'
226 'The only history variables available will be _,__,___ and _1\n'
225 'The only history variables available will be _,__,___ and _1\n'
227 'with the current result.')
226 'with the current result.')
228
227
229 self.flush()
228 self.flush()
230 # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
229 # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
231 # we cause buggy behavior for things like gettext).
230 # we cause buggy behavior for things like gettext).
232 if '_' not in __builtin__.__dict__:
231 if '_' not in __builtin__.__dict__:
233 self.___ = self.__
232 self.___ = self.__
234 self.__ = self._
233 self.__ = self._
235 self._ = result
234 self._ = result
236 self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
235 self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
237
236
238 # hackish access to top-level namespace to create _1,_2... dynamically
237 # hackish access to top-level namespace to create _1,_2... dynamically
239 to_main = {}
238 to_main = {}
240 if self.do_full_cache:
239 if self.do_full_cache:
241 new_result = '_'+`self.prompt_count`
240 new_result = '_'+`self.prompt_count`
242 to_main[new_result] = result
241 to_main[new_result] = result
243 self.shell.user_ns.update(to_main)
242 self.shell.user_ns.update(to_main)
244 self.shell.user_ns['_oh'][self.prompt_count] = result
243 self.shell.user_ns['_oh'][self.prompt_count] = result
245
244
246 def log_output(self, result):
245 def log_output(self, result):
247 """Log the output."""
246 """Log the output."""
248 if self.shell.logger.log_output:
247 if self.shell.logger.log_output:
249 self.shell.logger.log_write(repr(result),'output')
248 self.shell.logger.log_write(repr(result),'output')
250
249
251 def finish_displayhook(self):
250 def finish_displayhook(self):
252 """Finish up all displayhook activities."""
251 """Finish up all displayhook activities."""
253 IPython.utils.io.Term.cout.write(self.output_sep2)
252 IPython.utils.io.Term.cout.write(self.output_sep2)
254 IPython.utils.io.Term.cout.flush()
253 IPython.utils.io.Term.cout.flush()
255
254
256 def __call__(self, result=None):
255 def __call__(self, result=None):
257 """Printing with history cache management.
256 """Printing with history cache management.
258
257
259 This is invoked everytime the interpreter needs to print, and is
258 This is invoked everytime the interpreter needs to print, and is
260 activated by setting the variable sys.displayhook to it.
259 activated by setting the variable sys.displayhook to it.
261 """
260 """
262 self.check_for_underscore()
261 self.check_for_underscore()
263 if result is not None and not self.quiet():
262 if result is not None and not self.quiet():
264 self.start_displayhook()
263 self.start_displayhook()
265 self.write_output_prompt()
264 self.write_output_prompt()
266 result, result_repr = self.compute_result_repr(result)
265 result, result_repr = self.compute_result_repr(result)
267 self.write_result_repr(result_repr)
266 self.write_result_repr(result_repr)
268 self.update_user_ns(result)
267 self.update_user_ns(result)
269 self.log_output(result)
268 self.log_output(result)
270 self.finish_displayhook()
269 self.finish_displayhook()
271
270
272 def flush(self):
271 def flush(self):
273 if not self.do_full_cache:
272 if not self.do_full_cache:
274 raise ValueError,"You shouldn't have reached the cache flush "\
273 raise ValueError,"You shouldn't have reached the cache flush "\
275 "if full caching is not enabled!"
274 "if full caching is not enabled!"
276 # delete auto-generated vars from global namespace
275 # delete auto-generated vars from global namespace
277
276
278 for n in range(1,self.prompt_count + 1):
277 for n in range(1,self.prompt_count + 1):
279 key = '_'+`n`
278 key = '_'+`n`
280 try:
279 try:
281 del self.shell.user_ns[key]
280 del self.shell.user_ns[key]
282 except: pass
281 except: pass
283 self.shell.user_ns['_oh'].clear()
282 self.shell.user_ns['_oh'].clear()
284
283
285 if '_' not in __builtin__.__dict__:
284 if '_' not in __builtin__.__dict__:
286 self.shell.user_ns.update({'_':None,'__':None, '___':None})
285 self.shell.user_ns.update({'_':None,'__':None, '___':None})
287 import gc
286 import gc
288 gc.collect() # xxx needed?
287 gc.collect() # xxx needed?
289
288
@@ -1,437 +1,444 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Classes for handling input/output prompts.
2 """Classes for handling input/output prompts.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian Granger
7 * Brian Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2010 The IPython Development Team
11 # Copyright (C) 2008-2010 The IPython Development Team
12 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
12 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import os
22 import os
23 import re
23 import re
24 import socket
24 import socket
25 import sys
25 import sys
26
26
27 from IPython.core import release
27 from IPython.core import release
28 from IPython.external.Itpl import ItplNS
28 from IPython.external.Itpl import ItplNS
29 from IPython.utils import coloransi
29 from IPython.utils import coloransi
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Color schemes for prompts
32 # Color schemes for prompts
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 PromptColors = coloransi.ColorSchemeTable()
35 PromptColors = coloransi.ColorSchemeTable()
36 InputColors = coloransi.InputTermColors # just a shorthand
36 InputColors = coloransi.InputTermColors # just a shorthand
37 Colors = coloransi.TermColors # just a shorthand
37 Colors = coloransi.TermColors # just a shorthand
38
38
39 PromptColors.add_scheme(coloransi.ColorScheme(
39 PromptColors.add_scheme(coloransi.ColorScheme(
40 'NoColor',
40 'NoColor',
41 in_prompt = InputColors.NoColor, # Input prompt
41 in_prompt = InputColors.NoColor, # Input prompt
42 in_number = InputColors.NoColor, # Input prompt number
42 in_number = InputColors.NoColor, # Input prompt number
43 in_prompt2 = InputColors.NoColor, # Continuation prompt
43 in_prompt2 = InputColors.NoColor, # Continuation prompt
44 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
44 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
45
45
46 out_prompt = Colors.NoColor, # Output prompt
46 out_prompt = Colors.NoColor, # Output prompt
47 out_number = Colors.NoColor, # Output prompt number
47 out_number = Colors.NoColor, # Output prompt number
48
48
49 normal = Colors.NoColor # color off (usu. Colors.Normal)
49 normal = Colors.NoColor # color off (usu. Colors.Normal)
50 ))
50 ))
51
51
52 # make some schemes as instances so we can copy them for modification easily:
52 # make some schemes as instances so we can copy them for modification easily:
53 __PColLinux = coloransi.ColorScheme(
53 __PColLinux = coloransi.ColorScheme(
54 'Linux',
54 'Linux',
55 in_prompt = InputColors.Green,
55 in_prompt = InputColors.Green,
56 in_number = InputColors.LightGreen,
56 in_number = InputColors.LightGreen,
57 in_prompt2 = InputColors.Green,
57 in_prompt2 = InputColors.Green,
58 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
58 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
59
59
60 out_prompt = Colors.Red,
60 out_prompt = Colors.Red,
61 out_number = Colors.LightRed,
61 out_number = Colors.LightRed,
62
62
63 normal = Colors.Normal
63 normal = Colors.Normal
64 )
64 )
65 # Don't forget to enter it into the table!
65 # Don't forget to enter it into the table!
66 PromptColors.add_scheme(__PColLinux)
66 PromptColors.add_scheme(__PColLinux)
67
67
68 # Slightly modified Linux for light backgrounds
68 # Slightly modified Linux for light backgrounds
69 __PColLightBG = __PColLinux.copy('LightBG')
69 __PColLightBG = __PColLinux.copy('LightBG')
70
70
71 __PColLightBG.colors.update(
71 __PColLightBG.colors.update(
72 in_prompt = InputColors.Blue,
72 in_prompt = InputColors.Blue,
73 in_number = InputColors.LightBlue,
73 in_number = InputColors.LightBlue,
74 in_prompt2 = InputColors.Blue
74 in_prompt2 = InputColors.Blue
75 )
75 )
76 PromptColors.add_scheme(__PColLightBG)
76 PromptColors.add_scheme(__PColLightBG)
77
77
78 del Colors,InputColors
78 del Colors,InputColors
79
79
80 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
81 # Utilities
81 # Utilities
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83
83
84 def multiple_replace(dict, text):
84 def multiple_replace(dict, text):
85 """ Replace in 'text' all occurences of any key in the given
85 """ Replace in 'text' all occurences of any key in the given
86 dictionary by its corresponding value. Returns the new string."""
86 dictionary by its corresponding value. Returns the new string."""
87
87
88 # Function by Xavier Defrang, originally found at:
88 # Function by Xavier Defrang, originally found at:
89 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
89 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
90
90
91 # Create a regular expression from the dictionary keys
91 # Create a regular expression from the dictionary keys
92 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
92 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
93 # For each match, look-up corresponding value in dictionary
93 # For each match, look-up corresponding value in dictionary
94 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
94 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
95
95
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97 # Special characters that can be used in prompt templates, mainly bash-like
97 # Special characters that can be used in prompt templates, mainly bash-like
98 #-----------------------------------------------------------------------------
98 #-----------------------------------------------------------------------------
99
99
100 # If $HOME isn't defined (Windows), make it an absurd string so that it can
100 # If $HOME isn't defined (Windows), make it an absurd string so that it can
101 # never be expanded out into '~'. Basically anything which can never be a
101 # never be expanded out into '~'. Basically anything which can never be a
102 # reasonable directory name will do, we just want the $HOME -> '~' operation
102 # reasonable directory name will do, we just want the $HOME -> '~' operation
103 # to become a no-op. We pre-compute $HOME here so it's not done on every
103 # to become a no-op. We pre-compute $HOME here so it's not done on every
104 # prompt call.
104 # prompt call.
105
105
106 # FIXME:
106 # FIXME:
107
107
108 # - This should be turned into a class which does proper namespace management,
108 # - This should be turned into a class which does proper namespace management,
109 # since the prompt specials need to be evaluated in a certain namespace.
109 # since the prompt specials need to be evaluated in a certain namespace.
110 # Currently it's just globals, which need to be managed manually by code
110 # Currently it's just globals, which need to be managed manually by code
111 # below.
111 # below.
112
112
113 # - I also need to split up the color schemes from the prompt specials
113 # - I also need to split up the color schemes from the prompt specials
114 # somehow. I don't have a clean design for that quite yet.
114 # somehow. I don't have a clean design for that quite yet.
115
115
116 HOME = os.environ.get("HOME","//////:::::ZZZZZ,,,~~~")
116 HOME = os.environ.get("HOME","//////:::::ZZZZZ,,,~~~")
117
117
118 # We precompute a few more strings here for the prompt_specials, which are
118 # We precompute a few more strings here for the prompt_specials, which are
119 # fixed once ipython starts. This reduces the runtime overhead of computing
119 # fixed once ipython starts. This reduces the runtime overhead of computing
120 # prompt strings.
120 # prompt strings.
121 USER = os.environ.get("USER")
121 USER = os.environ.get("USER")
122 HOSTNAME = socket.gethostname()
122 HOSTNAME = socket.gethostname()
123 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
123 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
124 ROOT_SYMBOL = "$#"[os.name=='nt' or os.getuid()==0]
124 ROOT_SYMBOL = "$#"[os.name=='nt' or os.getuid()==0]
125
125
126 prompt_specials_color = {
126 prompt_specials_color = {
127 # Prompt/history count
127 # Prompt/history count
128 '%n' : '${self.col_num}' '${self.cache.prompt_count}' '${self.col_p}',
128 '%n' : '${self.col_num}' '${self.cache.prompt_count}' '${self.col_p}',
129 r'\#': '${self.col_num}' '${self.cache.prompt_count}' '${self.col_p}',
129 r'\#': '${self.col_num}' '${self.cache.prompt_count}' '${self.col_p}',
130 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
130 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
131 # can get numbers displayed in whatever color they want.
131 # can get numbers displayed in whatever color they want.
132 r'\N': '${self.cache.prompt_count}',
132 r'\N': '${self.cache.prompt_count}',
133
133
134 # Prompt/history count, with the actual digits replaced by dots. Used
134 # Prompt/history count, with the actual digits replaced by dots. Used
135 # mainly in continuation prompts (prompt_in2)
135 # mainly in continuation prompts (prompt_in2)
136 #r'\D': '${"."*len(str(self.cache.prompt_count))}',
136 #r'\D': '${"."*len(str(self.cache.prompt_count))}',
137
137
138 # More robust form of the above expression, that uses the __builtin__
138 # More robust form of the above expression, that uses the __builtin__
139 # module. Note that we can NOT use __builtins__ (note the 's'), because
139 # module. Note that we can NOT use __builtins__ (note the 's'), because
140 # that can either be a dict or a module, and can even mutate at runtime,
140 # that can either be a dict or a module, and can even mutate at runtime,
141 # depending on the context (Python makes no guarantees on it). In
141 # depending on the context (Python makes no guarantees on it). In
142 # contrast, __builtin__ is always a module object, though it must be
142 # contrast, __builtin__ is always a module object, though it must be
143 # explicitly imported.
143 # explicitly imported.
144 r'\D': '${"."*__builtin__.len(__builtin__.str(self.cache.prompt_count))}',
144 r'\D': '${"."*__builtin__.len(__builtin__.str(self.cache.prompt_count))}',
145
145
146 # Current working directory
146 # Current working directory
147 r'\w': '${os.getcwd()}',
147 r'\w': '${os.getcwd()}',
148 # Current time
148 # Current time
149 r'\t' : '${time.strftime("%H:%M:%S")}',
149 r'\t' : '${time.strftime("%H:%M:%S")}',
150 # Basename of current working directory.
150 # Basename of current working directory.
151 # (use os.sep to make this portable across OSes)
151 # (use os.sep to make this portable across OSes)
152 r'\W' : '${os.getcwd().split("%s")[-1]}' % os.sep,
152 r'\W' : '${os.getcwd().split("%s")[-1]}' % os.sep,
153 # These X<N> are an extension to the normal bash prompts. They return
153 # These X<N> are an extension to the normal bash prompts. They return
154 # N terms of the path, after replacing $HOME with '~'
154 # N terms of the path, after replacing $HOME with '~'
155 r'\X0': '${os.getcwd().replace("%s","~")}' % HOME,
155 r'\X0': '${os.getcwd().replace("%s","~")}' % HOME,
156 r'\X1': '${self.cwd_filt(1)}',
156 r'\X1': '${self.cwd_filt(1)}',
157 r'\X2': '${self.cwd_filt(2)}',
157 r'\X2': '${self.cwd_filt(2)}',
158 r'\X3': '${self.cwd_filt(3)}',
158 r'\X3': '${self.cwd_filt(3)}',
159 r'\X4': '${self.cwd_filt(4)}',
159 r'\X4': '${self.cwd_filt(4)}',
160 r'\X5': '${self.cwd_filt(5)}',
160 r'\X5': '${self.cwd_filt(5)}',
161 # Y<N> are similar to X<N>, but they show '~' if it's the directory
161 # Y<N> are similar to X<N>, but they show '~' if it's the directory
162 # N+1 in the list. Somewhat like %cN in tcsh.
162 # N+1 in the list. Somewhat like %cN in tcsh.
163 r'\Y0': '${self.cwd_filt2(0)}',
163 r'\Y0': '${self.cwd_filt2(0)}',
164 r'\Y1': '${self.cwd_filt2(1)}',
164 r'\Y1': '${self.cwd_filt2(1)}',
165 r'\Y2': '${self.cwd_filt2(2)}',
165 r'\Y2': '${self.cwd_filt2(2)}',
166 r'\Y3': '${self.cwd_filt2(3)}',
166 r'\Y3': '${self.cwd_filt2(3)}',
167 r'\Y4': '${self.cwd_filt2(4)}',
167 r'\Y4': '${self.cwd_filt2(4)}',
168 r'\Y5': '${self.cwd_filt2(5)}',
168 r'\Y5': '${self.cwd_filt2(5)}',
169 # Hostname up to first .
169 # Hostname up to first .
170 r'\h': HOSTNAME_SHORT,
170 r'\h': HOSTNAME_SHORT,
171 # Full hostname
171 # Full hostname
172 r'\H': HOSTNAME,
172 r'\H': HOSTNAME,
173 # Username of current user
173 # Username of current user
174 r'\u': USER,
174 r'\u': USER,
175 # Escaped '\'
175 # Escaped '\'
176 '\\\\': '\\',
176 '\\\\': '\\',
177 # Newline
177 # Newline
178 r'\n': '\n',
178 r'\n': '\n',
179 # Carriage return
179 # Carriage return
180 r'\r': '\r',
180 r'\r': '\r',
181 # Release version
181 # Release version
182 r'\v': release.version,
182 r'\v': release.version,
183 # Root symbol ($ or #)
183 # Root symbol ($ or #)
184 r'\$': ROOT_SYMBOL,
184 r'\$': ROOT_SYMBOL,
185 }
185 }
186
186
187 # A copy of the prompt_specials dictionary but with all color escapes removed,
187 # A copy of the prompt_specials dictionary but with all color escapes removed,
188 # so we can correctly compute the prompt length for the auto_rewrite method.
188 # so we can correctly compute the prompt length for the auto_rewrite method.
189 prompt_specials_nocolor = prompt_specials_color.copy()
189 prompt_specials_nocolor = prompt_specials_color.copy()
190 prompt_specials_nocolor['%n'] = '${self.cache.prompt_count}'
190 prompt_specials_nocolor['%n'] = '${self.cache.prompt_count}'
191 prompt_specials_nocolor[r'\#'] = '${self.cache.prompt_count}'
191 prompt_specials_nocolor[r'\#'] = '${self.cache.prompt_count}'
192
192
193 # Add in all the InputTermColors color escapes as valid prompt characters.
193 # Add in all the InputTermColors color escapes as valid prompt characters.
194 # They all get added as \\C_COLORNAME, so that we don't have any conflicts
194 # They all get added as \\C_COLORNAME, so that we don't have any conflicts
195 # with a color name which may begin with a letter used by any other of the
195 # with a color name which may begin with a letter used by any other of the
196 # allowed specials. This of course means that \\C will never be allowed for
196 # allowed specials. This of course means that \\C will never be allowed for
197 # anything else.
197 # anything else.
198 input_colors = coloransi.InputTermColors
198 input_colors = coloransi.InputTermColors
199 for _color in dir(input_colors):
199 for _color in dir(input_colors):
200 if _color[0] != '_':
200 if _color[0] != '_':
201 c_name = r'\C_'+_color
201 c_name = r'\C_'+_color
202 prompt_specials_color[c_name] = getattr(input_colors,_color)
202 prompt_specials_color[c_name] = getattr(input_colors,_color)
203 prompt_specials_nocolor[c_name] = ''
203 prompt_specials_nocolor[c_name] = ''
204
204
205 # we default to no color for safety. Note that prompt_specials is a global
205 # we default to no color for safety. Note that prompt_specials is a global
206 # variable used by all prompt objects.
206 # variable used by all prompt objects.
207 prompt_specials = prompt_specials_nocolor
207 prompt_specials = prompt_specials_nocolor
208
208
209 #-----------------------------------------------------------------------------
209 #-----------------------------------------------------------------------------
210 # More utilities
210 # More utilities
211 #-----------------------------------------------------------------------------
211 #-----------------------------------------------------------------------------
212
212
213 def str_safe(arg):
213 def str_safe(arg):
214 """Convert to a string, without ever raising an exception.
214 """Convert to a string, without ever raising an exception.
215
215
216 If str(arg) fails, <ERROR: ... > is returned, where ... is the exception
216 If str(arg) fails, <ERROR: ... > is returned, where ... is the exception
217 error message."""
217 error message."""
218
218
219 try:
219 try:
220 out = str(arg)
220 out = str(arg)
221 except UnicodeError:
221 except UnicodeError:
222 try:
222 try:
223 out = arg.encode('utf_8','replace')
223 out = arg.encode('utf_8','replace')
224 except Exception,msg:
224 except Exception,msg:
225 # let's keep this little duplication here, so that the most common
225 # let's keep this little duplication here, so that the most common
226 # case doesn't suffer from a double try wrapping.
226 # case doesn't suffer from a double try wrapping.
227 out = '<ERROR: %s>' % msg
227 out = '<ERROR: %s>' % msg
228 except Exception,msg:
228 except Exception,msg:
229 out = '<ERROR: %s>' % msg
229 out = '<ERROR: %s>' % msg
230 #raise # dbg
230 #raise # dbg
231 return out
231 return out
232
232
233 #-----------------------------------------------------------------------------
233 #-----------------------------------------------------------------------------
234 # Prompt classes
234 # Prompt classes
235 #-----------------------------------------------------------------------------
235 #-----------------------------------------------------------------------------
236
236
237 class BasePrompt(object):
237 class BasePrompt(object):
238 """Interactive prompt similar to Mathematica's."""
238 """Interactive prompt similar to Mathematica's."""
239
239
240 def _get_p_template(self):
240 def _get_p_template(self):
241 return self._p_template
241 return self._p_template
242
242
243 def _set_p_template(self,val):
243 def _set_p_template(self,val):
244 self._p_template = val
244 self._p_template = val
245 self.set_p_str()
245 self.set_p_str()
246
246
247 p_template = property(_get_p_template,_set_p_template,
247 p_template = property(_get_p_template,_set_p_template,
248 doc='Template for prompt string creation')
248 doc='Template for prompt string creation')
249
249
250 def __init__(self, cache, sep, prompt, pad_left=False):
250 def __init__(self, cache, sep, prompt, pad_left=False):
251
251
252 # Hack: we access information about the primary prompt through the
252 # Hack: we access information about the primary prompt through the
253 # cache argument. We need this, because we want the secondary prompt
253 # cache argument. We need this, because we want the secondary prompt
254 # to be aligned with the primary one. Color table info is also shared
254 # to be aligned with the primary one. Color table info is also shared
255 # by all prompt classes through the cache. Nice OO spaghetti code!
255 # by all prompt classes through the cache. Nice OO spaghetti code!
256 self.cache = cache
256 self.cache = cache
257 self.sep = sep
257 self.sep = sep
258
258
259 # regexp to count the number of spaces at the end of a prompt
259 # regexp to count the number of spaces at the end of a prompt
260 # expression, useful for prompt auto-rewriting
260 # expression, useful for prompt auto-rewriting
261 self.rspace = re.compile(r'(\s*)$')
261 self.rspace = re.compile(r'(\s*)$')
262 # Flag to left-pad prompt strings to match the length of the primary
262 # Flag to left-pad prompt strings to match the length of the primary
263 # prompt
263 # prompt
264 self.pad_left = pad_left
264 self.pad_left = pad_left
265
265
266 # Set template to create each actual prompt (where numbers change).
266 # Set template to create each actual prompt (where numbers change).
267 # Use a property
267 # Use a property
268 self.p_template = prompt
268 self.p_template = prompt
269 self.set_p_str()
269 self.set_p_str()
270
270
271 def set_p_str(self):
271 def set_p_str(self):
272 """ Set the interpolating prompt strings.
272 """ Set the interpolating prompt strings.
273
273
274 This must be called every time the color settings change, because the
274 This must be called every time the color settings change, because the
275 prompt_specials global may have changed."""
275 prompt_specials global may have changed."""
276
276
277 import os,time # needed in locals for prompt string handling
277 import os,time # needed in locals for prompt string handling
278 loc = locals()
278 loc = locals()
279 try:
279 try:
280 self.p_str = ItplNS('%s%s%s' %
280 self.p_str = ItplNS('%s%s%s' %
281 ('${self.sep}${self.col_p}',
281 ('${self.sep}${self.col_p}',
282 multiple_replace(prompt_specials, self.p_template),
282 multiple_replace(prompt_specials, self.p_template),
283 '${self.col_norm}'),self.cache.shell.user_ns,loc)
283 '${self.col_norm}'),self.cache.shell.user_ns,loc)
284
284
285 self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor,
285 self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor,
286 self.p_template),
286 self.p_template),
287 self.cache.shell.user_ns,loc)
287 self.cache.shell.user_ns,loc)
288 except:
288 except:
289 print "Illegal prompt template (check $ usage!):",self.p_template
289 print "Illegal prompt template (check $ usage!):",self.p_template
290 self.p_str = self.p_template
290 self.p_str = self.p_template
291 self.p_str_nocolor = self.p_template
291 self.p_str_nocolor = self.p_template
292
292
293 def write(self, msg):
293 def write(self, msg):
294 sys.stdout.write(msg)
294 sys.stdout.write(msg)
295 return ''
295 return ''
296
296
297 def __str__(self):
297 def __str__(self):
298 """Return a string form of the prompt.
298 """Return a string form of the prompt.
299
299
300 This for is useful for continuation and output prompts, since it is
300 This for is useful for continuation and output prompts, since it is
301 left-padded to match lengths with the primary one (if the
301 left-padded to match lengths with the primary one (if the
302 self.pad_left attribute is set)."""
302 self.pad_left attribute is set)."""
303
303
304 out_str = str_safe(self.p_str)
304 out_str = str_safe(self.p_str)
305 if self.pad_left:
305 if self.pad_left:
306 # We must find the amount of padding required to match lengths,
306 # We must find the amount of padding required to match lengths,
307 # taking the color escapes (which are invisible on-screen) into
307 # taking the color escapes (which are invisible on-screen) into
308 # account.
308 # account.
309 esc_pad = len(out_str) - len(str_safe(self.p_str_nocolor))
309 esc_pad = len(out_str) - len(str_safe(self.p_str_nocolor))
310 format = '%%%ss' % (len(str(self.cache.last_prompt))+esc_pad)
310 format = '%%%ss' % (len(str(self.cache.last_prompt))+esc_pad)
311 return format % out_str
311 return format % out_str
312 else:
312 else:
313 return out_str
313 return out_str
314
314
315 # these path filters are put in as methods so that we can control the
315 # these path filters are put in as methods so that we can control the
316 # namespace where the prompt strings get evaluated
316 # namespace where the prompt strings get evaluated
317 def cwd_filt(self, depth):
317 def cwd_filt(self, depth):
318 """Return the last depth elements of the current working directory.
318 """Return the last depth elements of the current working directory.
319
319
320 $HOME is always replaced with '~'.
320 $HOME is always replaced with '~'.
321 If depth==0, the full path is returned."""
321 If depth==0, the full path is returned."""
322
322
323 cwd = os.getcwd().replace(HOME,"~")
323 cwd = os.getcwd().replace(HOME,"~")
324 out = os.sep.join(cwd.split(os.sep)[-depth:])
324 out = os.sep.join(cwd.split(os.sep)[-depth:])
325 if out:
325 if out:
326 return out
326 return out
327 else:
327 else:
328 return os.sep
328 return os.sep
329
329
330 def cwd_filt2(self, depth):
330 def cwd_filt2(self, depth):
331 """Return the last depth elements of the current working directory.
331 """Return the last depth elements of the current working directory.
332
332
333 $HOME is always replaced with '~'.
333 $HOME is always replaced with '~'.
334 If depth==0, the full path is returned."""
334 If depth==0, the full path is returned."""
335
335
336 full_cwd = os.getcwd()
336 full_cwd = os.getcwd()
337 cwd = full_cwd.replace(HOME,"~").split(os.sep)
337 cwd = full_cwd.replace(HOME,"~").split(os.sep)
338 if '~' in cwd and len(cwd) == depth+1:
338 if '~' in cwd and len(cwd) == depth+1:
339 depth += 1
339 depth += 1
340 drivepart = ''
340 drivepart = ''
341 if sys.platform == 'win32' and len(cwd) > depth:
341 if sys.platform == 'win32' and len(cwd) > depth:
342 drivepart = os.path.splitdrive(full_cwd)[0]
342 drivepart = os.path.splitdrive(full_cwd)[0]
343 out = drivepart + '/'.join(cwd[-depth:])
343 out = drivepart + '/'.join(cwd[-depth:])
344
344
345 if out:
345 if out:
346 return out
346 return out
347 else:
347 else:
348 return os.sep
348 return os.sep
349
349
350 def __nonzero__(self):
350 def __nonzero__(self):
351 """Implement boolean behavior.
351 """Implement boolean behavior.
352
352
353 Checks whether the p_str attribute is non-empty"""
353 Checks whether the p_str attribute is non-empty"""
354
354
355 return bool(self.p_template)
355 return bool(self.p_template)
356
356
357
357
358 class Prompt1(BasePrompt):
358 class Prompt1(BasePrompt):
359 """Input interactive prompt similar to Mathematica's."""
359 """Input interactive prompt similar to Mathematica's."""
360
360
361 def __init__(self, cache, sep='\n', prompt='In [\\#]: ', pad_left=True):
361 def __init__(self, cache, sep='\n', prompt='In [\\#]: ', pad_left=True):
362 BasePrompt.__init__(self, cache, sep, prompt, pad_left)
362 BasePrompt.__init__(self, cache, sep, prompt, pad_left)
363
363
364 def set_colors(self):
364 def set_colors(self):
365 self.set_p_str()
365 self.set_p_str()
366 Colors = self.cache.color_table.active_colors # shorthand
366 Colors = self.cache.color_table.active_colors # shorthand
367 self.col_p = Colors.in_prompt
367 self.col_p = Colors.in_prompt
368 self.col_num = Colors.in_number
368 self.col_num = Colors.in_number
369 self.col_norm = Colors.in_normal
369 self.col_norm = Colors.in_normal
370 # We need a non-input version of these escapes for the '--->'
370 # We need a non-input version of these escapes for the '--->'
371 # auto-call prompts used in the auto_rewrite() method.
371 # auto-call prompts used in the auto_rewrite() method.
372 self.col_p_ni = self.col_p.replace('\001','').replace('\002','')
372 self.col_p_ni = self.col_p.replace('\001','').replace('\002','')
373 self.col_norm_ni = Colors.normal
373 self.col_norm_ni = Colors.normal
374
374
375 def peek_next_prompt(self):
376 """Get the next prompt, but don't increment the counter."""
377 self.cache.prompt_count += 1
378 next_prompt = str_safe(self.p_str)
379 self.cache.prompt_count -= 1
380 return next_prompt
381
375 def __str__(self):
382 def __str__(self):
376 self.cache.prompt_count += 1
383 self.cache.prompt_count += 1
377 self.cache.last_prompt = str_safe(self.p_str_nocolor).split('\n')[-1]
384 self.cache.last_prompt = str_safe(self.p_str_nocolor).split('\n')[-1]
378 return str_safe(self.p_str)
385 return str_safe(self.p_str)
379
386
380 def auto_rewrite(self):
387 def auto_rewrite(self):
381 """Print a string of the form '--->' which lines up with the previous
388 """Print a string of the form '--->' which lines up with the previous
382 input string. Useful for systems which re-write the user input when
389 input string. Useful for systems which re-write the user input when
383 handling automatically special syntaxes."""
390 handling automatically special syntaxes."""
384
391
385 curr = str(self.cache.last_prompt)
392 curr = str(self.cache.last_prompt)
386 nrspaces = len(self.rspace.search(curr).group())
393 nrspaces = len(self.rspace.search(curr).group())
387 return '%s%s>%s%s' % (self.col_p_ni,'-'*(len(curr)-nrspaces-1),
394 return '%s%s>%s%s' % (self.col_p_ni,'-'*(len(curr)-nrspaces-1),
388 ' '*nrspaces,self.col_norm_ni)
395 ' '*nrspaces,self.col_norm_ni)
389
396
390
397
391 class PromptOut(BasePrompt):
398 class PromptOut(BasePrompt):
392 """Output interactive prompt similar to Mathematica's."""
399 """Output interactive prompt similar to Mathematica's."""
393
400
394 def __init__(self, cache, sep='', prompt='Out[\\#]: ', pad_left=True):
401 def __init__(self, cache, sep='', prompt='Out[\\#]: ', pad_left=True):
395 BasePrompt.__init__(self, cache, sep, prompt, pad_left)
402 BasePrompt.__init__(self, cache, sep, prompt, pad_left)
396 if not self.p_template:
403 if not self.p_template:
397 self.__str__ = lambda: ''
404 self.__str__ = lambda: ''
398
405
399 def set_colors(self):
406 def set_colors(self):
400 self.set_p_str()
407 self.set_p_str()
401 Colors = self.cache.color_table.active_colors # shorthand
408 Colors = self.cache.color_table.active_colors # shorthand
402 self.col_p = Colors.out_prompt
409 self.col_p = Colors.out_prompt
403 self.col_num = Colors.out_number
410 self.col_num = Colors.out_number
404 self.col_norm = Colors.normal
411 self.col_norm = Colors.normal
405
412
406
413
407 class Prompt2(BasePrompt):
414 class Prompt2(BasePrompt):
408 """Interactive continuation prompt."""
415 """Interactive continuation prompt."""
409
416
410 def __init__(self, cache, prompt=' .\\D.: ', pad_left=True):
417 def __init__(self, cache, prompt=' .\\D.: ', pad_left=True):
411 self.cache = cache
418 self.cache = cache
412 self.p_template = prompt
419 self.p_template = prompt
413 self.pad_left = pad_left
420 self.pad_left = pad_left
414 self.set_p_str()
421 self.set_p_str()
415
422
416 def set_p_str(self):
423 def set_p_str(self):
417 import os,time # needed in locals for prompt string handling
424 import os,time # needed in locals for prompt string handling
418 loc = locals()
425 loc = locals()
419 self.p_str = ItplNS('%s%s%s' %
426 self.p_str = ItplNS('%s%s%s' %
420 ('${self.col_p2}',
427 ('${self.col_p2}',
421 multiple_replace(prompt_specials, self.p_template),
428 multiple_replace(prompt_specials, self.p_template),
422 '$self.col_norm'),
429 '$self.col_norm'),
423 self.cache.shell.user_ns,loc)
430 self.cache.shell.user_ns,loc)
424 self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor,
431 self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor,
425 self.p_template),
432 self.p_template),
426 self.cache.shell.user_ns,loc)
433 self.cache.shell.user_ns,loc)
427
434
428 def set_colors(self):
435 def set_colors(self):
429 self.set_p_str()
436 self.set_p_str()
430 Colors = self.cache.color_table.active_colors
437 Colors = self.cache.color_table.active_colors
431 self.col_p2 = Colors.in_prompt2
438 self.col_p2 = Colors.in_prompt2
432 self.col_norm = Colors.in_normal
439 self.col_norm = Colors.in_normal
433 # FIXME (2004-06-16) HACK: prevent crashes for users who haven't
440 # FIXME (2004-06-16) HACK: prevent crashes for users who haven't
434 # updated their prompt_in2 definitions. Remove eventually.
441 # updated their prompt_in2 definitions. Remove eventually.
435 self.col_p = Colors.out_prompt
442 self.col_p = Colors.out_prompt
436 self.col_num = Colors.out_number
443 self.col_num = Colors.out_number
437
444
@@ -1,384 +1,385 b''
1 # Standard library imports
1 # Standard library imports
2 import signal
2 import signal
3 import sys
3 import sys
4
4
5 # System library imports
5 # System library imports
6 from pygments.lexers import PythonLexer
6 from pygments.lexers import PythonLexer
7 from PyQt4 import QtCore, QtGui
7 from PyQt4 import QtCore, QtGui
8 import zmq
8 import zmq
9
9
10 # Local imports
10 # Local imports
11 from IPython.core.inputsplitter import InputSplitter
11 from IPython.core.inputsplitter import InputSplitter
12 from call_tip_widget import CallTipWidget
12 from call_tip_widget import CallTipWidget
13 from completion_lexer import CompletionLexer
13 from completion_lexer import CompletionLexer
14 from console_widget import HistoryConsoleWidget
14 from console_widget import HistoryConsoleWidget
15 from pygments_highlighter import PygmentsHighlighter
15 from pygments_highlighter import PygmentsHighlighter
16
16
17
17
18 class FrontendHighlighter(PygmentsHighlighter):
18 class FrontendHighlighter(PygmentsHighlighter):
19 """ A PygmentsHighlighter that can be turned on and off and that ignores
19 """ A PygmentsHighlighter that can be turned on and off and that ignores
20 prompts.
20 prompts.
21 """
21 """
22
22
23 def __init__(self, frontend):
23 def __init__(self, frontend):
24 super(FrontendHighlighter, self).__init__(frontend._control.document())
24 super(FrontendHighlighter, self).__init__(frontend._control.document())
25 self._current_offset = 0
25 self._current_offset = 0
26 self._frontend = frontend
26 self._frontend = frontend
27 self.highlighting_on = False
27 self.highlighting_on = False
28
28
29 def highlightBlock(self, qstring):
29 def highlightBlock(self, qstring):
30 """ Highlight a block of text. Reimplemented to highlight selectively.
30 """ Highlight a block of text. Reimplemented to highlight selectively.
31 """
31 """
32 if not self.highlighting_on:
32 if not self.highlighting_on:
33 return
33 return
34
34
35 # The input to this function is unicode string that may contain
35 # The input to this function is unicode string that may contain
36 # paragraph break characters, non-breaking spaces, etc. Here we acquire
36 # paragraph break characters, non-breaking spaces, etc. Here we acquire
37 # the string as plain text so we can compare it.
37 # the string as plain text so we can compare it.
38 current_block = self.currentBlock()
38 current_block = self.currentBlock()
39 string = self._frontend._get_block_plain_text(current_block)
39 string = self._frontend._get_block_plain_text(current_block)
40
40
41 # Decide whether to check for the regular or continuation prompt.
41 # Decide whether to check for the regular or continuation prompt.
42 if current_block.contains(self._frontend._prompt_pos):
42 if current_block.contains(self._frontend._prompt_pos):
43 prompt = self._frontend._prompt
43 prompt = self._frontend._prompt
44 else:
44 else:
45 prompt = self._frontend._continuation_prompt
45 prompt = self._frontend._continuation_prompt
46
46
47 # Don't highlight the part of the string that contains the prompt.
47 # Don't highlight the part of the string that contains the prompt.
48 if string.startswith(prompt):
48 if string.startswith(prompt):
49 self._current_offset = len(prompt)
49 self._current_offset = len(prompt)
50 qstring.remove(0, len(prompt))
50 qstring.remove(0, len(prompt))
51 else:
51 else:
52 self._current_offset = 0
52 self._current_offset = 0
53
53
54 PygmentsHighlighter.highlightBlock(self, qstring)
54 PygmentsHighlighter.highlightBlock(self, qstring)
55
55
56 def setFormat(self, start, count, format):
56 def setFormat(self, start, count, format):
57 """ Reimplemented to highlight selectively.
57 """ Reimplemented to highlight selectively.
58 """
58 """
59 start += self._current_offset
59 start += self._current_offset
60 PygmentsHighlighter.setFormat(self, start, count, format)
60 PygmentsHighlighter.setFormat(self, start, count, format)
61
61
62
62
63 class FrontendWidget(HistoryConsoleWidget):
63 class FrontendWidget(HistoryConsoleWidget):
64 """ A Qt frontend for a generic Python kernel.
64 """ A Qt frontend for a generic Python kernel.
65 """
65 """
66
66
67 # Emitted when an 'execute_reply' is received from the kernel.
67 # Emitted when an 'execute_reply' is received from the kernel.
68 executed = QtCore.pyqtSignal(object)
68 executed = QtCore.pyqtSignal(object)
69
69
70 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
71 # 'object' interface
71 # 'object' interface
72 #---------------------------------------------------------------------------
72 #---------------------------------------------------------------------------
73
73
74 def __init__(self, *args, **kw):
74 def __init__(self, *args, **kw):
75 super(FrontendWidget, self).__init__(*args, **kw)
75 super(FrontendWidget, self).__init__(*args, **kw)
76
76
77 # FrontendWidget protected variables.
77 # FrontendWidget protected variables.
78 self._call_tip_widget = CallTipWidget(self._control)
78 self._call_tip_widget = CallTipWidget(self._control)
79 self._completion_lexer = CompletionLexer(PythonLexer())
79 self._completion_lexer = CompletionLexer(PythonLexer())
80 self._hidden = True
80 self._hidden = True
81 self._highlighter = FrontendHighlighter(self)
81 self._highlighter = FrontendHighlighter(self)
82 self._input_splitter = InputSplitter(input_mode='replace')
82 self._input_splitter = InputSplitter(input_mode='replace')
83 self._kernel_manager = None
83 self._kernel_manager = None
84
84
85 # Configure the ConsoleWidget.
85 # Configure the ConsoleWidget.
86 self.tab_width = 4
86 self.tab_width = 4
87 self._set_continuation_prompt('... ')
87 self._set_continuation_prompt('... ')
88
88
89 # Connect signal handlers.
89 # Connect signal handlers.
90 document = self._control.document()
90 document = self._control.document()
91 document.contentsChange.connect(self._document_contents_change)
91 document.contentsChange.connect(self._document_contents_change)
92
92
93 #---------------------------------------------------------------------------
93 #---------------------------------------------------------------------------
94 # 'QWidget' interface
94 # 'QWidget' interface
95 #---------------------------------------------------------------------------
95 #---------------------------------------------------------------------------
96
96
97 def focusOutEvent(self, event):
97 def focusOutEvent(self, event):
98 """ Reimplemented to hide calltips.
98 """ Reimplemented to hide calltips.
99 """
99 """
100 self._call_tip_widget.hide()
100 self._call_tip_widget.hide()
101 super(FrontendWidget, self).focusOutEvent(event)
101 super(FrontendWidget, self).focusOutEvent(event)
102
102
103 def keyPressEvent(self, event):
103 def keyPressEvent(self, event):
104 """ Reimplemented to allow calltips to process events and to send
104 """ Reimplemented to allow calltips to process events and to send
105 signals to the kernel.
105 signals to the kernel.
106 """
106 """
107 if self._executing and event.key() == QtCore.Qt.Key_C and \
107 if self._executing and event.key() == QtCore.Qt.Key_C and \
108 self._control_down(event.modifiers()):
108 self._control_down(event.modifiers()):
109 self._interrupt_kernel()
109 self._interrupt_kernel()
110 else:
110 else:
111 if self._call_tip_widget.isVisible():
111 if self._call_tip_widget.isVisible():
112 self._call_tip_widget.keyPressEvent(event)
112 self._call_tip_widget.keyPressEvent(event)
113 super(FrontendWidget, self).keyPressEvent(event)
113 super(FrontendWidget, self).keyPressEvent(event)
114
114
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116 # 'ConsoleWidget' abstract interface
116 # 'ConsoleWidget' abstract interface
117 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
118
118
119 def _is_complete(self, source, interactive):
119 def _is_complete(self, source, interactive):
120 """ Returns whether 'source' can be completely processed and a new
120 """ Returns whether 'source' can be completely processed and a new
121 prompt created. When triggered by an Enter/Return key press,
121 prompt created. When triggered by an Enter/Return key press,
122 'interactive' is True; otherwise, it is False.
122 'interactive' is True; otherwise, it is False.
123 """
123 """
124 complete = self._input_splitter.push(source.expandtabs(4))
124 complete = self._input_splitter.push(source.expandtabs(4))
125 if interactive:
125 if interactive:
126 complete = not self._input_splitter.push_accepts_more()
126 complete = not self._input_splitter.push_accepts_more()
127 return complete
127 return complete
128
128
129 def _execute(self, source, hidden):
129 def _execute(self, source, hidden):
130 """ Execute 'source'. If 'hidden', do not show any output.
130 """ Execute 'source'. If 'hidden', do not show any output.
131 """
131 """
132 self.kernel_manager.xreq_channel.execute(source)
132 self.kernel_manager.xreq_channel.execute(source)
133 self._hidden = hidden
133 self._hidden = hidden
134
134
135 def _prompt_started_hook(self):
135 def _prompt_started_hook(self):
136 """ Called immediately after a new prompt is displayed.
136 """ Called immediately after a new prompt is displayed.
137 """
137 """
138 if not self._reading:
138 if not self._reading:
139 self._highlighter.highlighting_on = True
139 self._highlighter.highlighting_on = True
140
140
141 # Auto-indent if this is a continuation prompt.
141 # Auto-indent if this is a continuation prompt.
142 if self._get_prompt_cursor().blockNumber() != \
142 if self._get_prompt_cursor().blockNumber() != \
143 self._get_end_cursor().blockNumber():
143 self._get_end_cursor().blockNumber():
144 spaces = self._input_splitter.indent_spaces
144 spaces = self._input_splitter.indent_spaces
145 self._append_plain_text('\t' * (spaces / self.tab_width))
145 self._append_plain_text('\t' * (spaces / self.tab_width))
146 self._append_plain_text(' ' * (spaces % self.tab_width))
146 self._append_plain_text(' ' * (spaces % self.tab_width))
147
147
148 def _prompt_finished_hook(self):
148 def _prompt_finished_hook(self):
149 """ Called immediately after a prompt is finished, i.e. when some input
149 """ Called immediately after a prompt is finished, i.e. when some input
150 will be processed and a new prompt displayed.
150 will be processed and a new prompt displayed.
151 """
151 """
152 if not self._reading:
152 if not self._reading:
153 self._highlighter.highlighting_on = False
153 self._highlighter.highlighting_on = False
154
154
155 def _tab_pressed(self):
155 def _tab_pressed(self):
156 """ Called when the tab key is pressed. Returns whether to continue
156 """ Called when the tab key is pressed. Returns whether to continue
157 processing the event.
157 processing the event.
158 """
158 """
159 self._keep_cursor_in_buffer()
159 self._keep_cursor_in_buffer()
160 cursor = self._get_cursor()
160 cursor = self._get_cursor()
161 return not self._complete()
161 return not self._complete()
162
162
163 #---------------------------------------------------------------------------
163 #---------------------------------------------------------------------------
164 # 'FrontendWidget' interface
164 # 'FrontendWidget' interface
165 #---------------------------------------------------------------------------
165 #---------------------------------------------------------------------------
166
166
167 def execute_file(self, path, hidden=False):
167 def execute_file(self, path, hidden=False):
168 """ Attempts to execute file with 'path'. If 'hidden', no output is
168 """ Attempts to execute file with 'path'. If 'hidden', no output is
169 shown.
169 shown.
170 """
170 """
171 self.execute('execfile("%s")' % path, hidden=hidden)
171 self.execute('execfile("%s")' % path, hidden=hidden)
172
172
173 def _get_kernel_manager(self):
173 def _get_kernel_manager(self):
174 """ Returns the current kernel manager.
174 """ Returns the current kernel manager.
175 """
175 """
176 return self._kernel_manager
176 return self._kernel_manager
177
177
178 def _set_kernel_manager(self, kernel_manager):
178 def _set_kernel_manager(self, kernel_manager):
179 """ Disconnect from the current kernel manager (if any) and set a new
179 """ Disconnect from the current kernel manager (if any) and set a new
180 kernel manager.
180 kernel manager.
181 """
181 """
182 # Disconnect the old kernel manager, if necessary.
182 # Disconnect the old kernel manager, if necessary.
183 if self._kernel_manager is not None:
183 if self._kernel_manager is not None:
184 self._kernel_manager.started_channels.disconnect(
184 self._kernel_manager.started_channels.disconnect(
185 self._started_channels)
185 self._started_channels)
186 self._kernel_manager.stopped_channels.disconnect(
186 self._kernel_manager.stopped_channels.disconnect(
187 self._stopped_channels)
187 self._stopped_channels)
188
188
189 # Disconnect the old kernel manager's channels.
189 # Disconnect the old kernel manager's channels.
190 sub = self._kernel_manager.sub_channel
190 sub = self._kernel_manager.sub_channel
191 xreq = self._kernel_manager.xreq_channel
191 xreq = self._kernel_manager.xreq_channel
192 rep = self._kernel_manager.rep_channel
192 rep = self._kernel_manager.rep_channel
193 sub.message_received.disconnect(self._handle_sub)
193 sub.message_received.disconnect(self._handle_sub)
194 xreq.execute_reply.disconnect(self._handle_execute_reply)
194 xreq.execute_reply.disconnect(self._handle_execute_reply)
195 xreq.complete_reply.disconnect(self._handle_complete_reply)
195 xreq.complete_reply.disconnect(self._handle_complete_reply)
196 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
196 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
197 rep.input_requested.disconnect(self._handle_req)
197 rep.input_requested.disconnect(self._handle_req)
198
198
199 # Handle the case where the old kernel manager is still listening.
199 # Handle the case where the old kernel manager is still listening.
200 if self._kernel_manager.channels_running:
200 if self._kernel_manager.channels_running:
201 self._stopped_channels()
201 self._stopped_channels()
202
202
203 # Set the new kernel manager.
203 # Set the new kernel manager.
204 self._kernel_manager = kernel_manager
204 self._kernel_manager = kernel_manager
205 if kernel_manager is None:
205 if kernel_manager is None:
206 return
206 return
207
207
208 # Connect the new kernel manager.
208 # Connect the new kernel manager.
209 kernel_manager.started_channels.connect(self._started_channels)
209 kernel_manager.started_channels.connect(self._started_channels)
210 kernel_manager.stopped_channels.connect(self._stopped_channels)
210 kernel_manager.stopped_channels.connect(self._stopped_channels)
211
211
212 # Connect the new kernel manager's channels.
212 # Connect the new kernel manager's channels.
213 sub = kernel_manager.sub_channel
213 sub = kernel_manager.sub_channel
214 xreq = kernel_manager.xreq_channel
214 xreq = kernel_manager.xreq_channel
215 rep = kernel_manager.rep_channel
215 rep = kernel_manager.rep_channel
216 sub.message_received.connect(self._handle_sub)
216 sub.message_received.connect(self._handle_sub)
217 xreq.execute_reply.connect(self._handle_execute_reply)
217 xreq.execute_reply.connect(self._handle_execute_reply)
218 xreq.complete_reply.connect(self._handle_complete_reply)
218 xreq.complete_reply.connect(self._handle_complete_reply)
219 xreq.object_info_reply.connect(self._handle_object_info_reply)
219 xreq.object_info_reply.connect(self._handle_object_info_reply)
220 rep.input_requested.connect(self._handle_req)
220 rep.input_requested.connect(self._handle_req)
221
221
222 # Handle the case where the kernel manager started channels before
222 # Handle the case where the kernel manager started channels before
223 # we connected.
223 # we connected.
224 if kernel_manager.channels_running:
224 if kernel_manager.channels_running:
225 self._started_channels()
225 self._started_channels()
226
226
227 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
227 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
228
228
229 #---------------------------------------------------------------------------
229 #---------------------------------------------------------------------------
230 # 'FrontendWidget' protected interface
230 # 'FrontendWidget' protected interface
231 #---------------------------------------------------------------------------
231 #---------------------------------------------------------------------------
232
232
233 def _call_tip(self):
233 def _call_tip(self):
234 """ Shows a call tip, if appropriate, at the current cursor location.
234 """ Shows a call tip, if appropriate, at the current cursor location.
235 """
235 """
236 # Decide if it makes sense to show a call tip
236 # Decide if it makes sense to show a call tip
237 cursor = self._get_cursor()
237 cursor = self._get_cursor()
238 cursor.movePosition(QtGui.QTextCursor.Left)
238 cursor.movePosition(QtGui.QTextCursor.Left)
239 document = self._control.document()
239 document = self._control.document()
240 if document.characterAt(cursor.position()).toAscii() != '(':
240 if document.characterAt(cursor.position()).toAscii() != '(':
241 return False
241 return False
242 context = self._get_context(cursor)
242 context = self._get_context(cursor)
243 if not context:
243 if not context:
244 return False
244 return False
245
245
246 # Send the metadata request to the kernel
246 # Send the metadata request to the kernel
247 name = '.'.join(context)
247 name = '.'.join(context)
248 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
248 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
249 self._calltip_pos = self._get_cursor().position()
249 self._calltip_pos = self._get_cursor().position()
250 return True
250 return True
251
251
252 def _complete(self):
252 def _complete(self):
253 """ Performs completion at the current cursor location.
253 """ Performs completion at the current cursor location.
254 """
254 """
255 # Decide if it makes sense to do completion
255 # Decide if it makes sense to do completion
256 context = self._get_context()
256 context = self._get_context()
257 if not context:
257 if not context:
258 return False
258 return False
259
259
260 # Send the completion request to the kernel
260 # Send the completion request to the kernel
261 text = '.'.join(context)
261 text = '.'.join(context)
262 self._complete_id = self.kernel_manager.xreq_channel.complete(
262 self._complete_id = self.kernel_manager.xreq_channel.complete(
263 text, self.input_buffer_cursor_line, self.input_buffer)
263 text, self.input_buffer_cursor_line, self.input_buffer)
264 self._complete_pos = self._get_cursor().position()
264 self._complete_pos = self._get_cursor().position()
265 return True
265 return True
266
266
267 def _get_banner(self):
267 def _get_banner(self):
268 """ Gets a banner to display at the beginning of a session.
268 """ Gets a banner to display at the beginning of a session.
269 """
269 """
270 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
270 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
271 '"license" for more information.'
271 '"license" for more information.'
272 return banner % (sys.version, sys.platform)
272 return banner % (sys.version, sys.platform)
273
273
274 def _get_context(self, cursor=None):
274 def _get_context(self, cursor=None):
275 """ Gets the context at the current cursor location.
275 """ Gets the context at the current cursor location.
276 """
276 """
277 if cursor is None:
277 if cursor is None:
278 cursor = self._get_cursor()
278 cursor = self._get_cursor()
279 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
279 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
280 QtGui.QTextCursor.KeepAnchor)
280 QtGui.QTextCursor.KeepAnchor)
281 text = str(cursor.selection().toPlainText())
281 text = str(cursor.selection().toPlainText())
282 return self._completion_lexer.get_context(text)
282 return self._completion_lexer.get_context(text)
283
283
284 def _interrupt_kernel(self):
284 def _interrupt_kernel(self):
285 """ Attempts to the interrupt the kernel.
285 """ Attempts to the interrupt the kernel.
286 """
286 """
287 if self.kernel_manager.has_kernel:
287 if self.kernel_manager.has_kernel:
288 self.kernel_manager.signal_kernel(signal.SIGINT)
288 self.kernel_manager.signal_kernel(signal.SIGINT)
289 else:
289 else:
290 self._append_plain_text('Kernel process is either remote or '
290 self._append_plain_text('Kernel process is either remote or '
291 'unspecified. Cannot interrupt.\n')
291 'unspecified. Cannot interrupt.\n')
292
292
293 def _show_interpreter_prompt(self):
293 def _show_interpreter_prompt(self):
294 """ Shows a prompt for the interpreter.
294 """ Shows a prompt for the interpreter.
295 """
295 """
296 self._show_prompt('>>> ')
296 self._show_prompt('>>> ')
297
297
298 #------ Signal handlers ----------------------------------------------------
298 #------ Signal handlers ----------------------------------------------------
299
299
300 def _started_channels(self):
300 def _started_channels(self):
301 """ Called when the kernel manager has started listening.
301 """ Called when the kernel manager has started listening.
302 """
302 """
303 self._reset()
303 self._reset()
304 self._append_plain_text(self._get_banner())
304 self._append_plain_text(self._get_banner())
305 self._show_interpreter_prompt()
305 self._show_interpreter_prompt()
306
306
307 def _stopped_channels(self):
307 def _stopped_channels(self):
308 """ Called when the kernel manager has stopped listening.
308 """ Called when the kernel manager has stopped listening.
309 """
309 """
310 # FIXME: Print a message here?
310 # FIXME: Print a message here?
311 pass
311 pass
312
312
313 def _document_contents_change(self, position, removed, added):
313 def _document_contents_change(self, position, removed, added):
314 """ Called whenever the document's content changes. Display a calltip
314 """ Called whenever the document's content changes. Display a calltip
315 if appropriate.
315 if appropriate.
316 """
316 """
317 # Calculate where the cursor should be *after* the change:
317 # Calculate where the cursor should be *after* the change:
318 position += added
318 position += added
319
319
320 document = self._control.document()
320 document = self._control.document()
321 if position == self._get_cursor().position():
321 if position == self._get_cursor().position():
322 self._call_tip()
322 self._call_tip()
323
323
324 def _handle_req(self, req):
324 def _handle_req(self, req):
325 # Make sure that all output from the SUB channel has been processed
325 # Make sure that all output from the SUB channel has been processed
326 # before entering readline mode.
326 # before entering readline mode.
327 self.kernel_manager.sub_channel.flush()
327 self.kernel_manager.sub_channel.flush()
328
328
329 def callback(line):
329 def callback(line):
330 self.kernel_manager.rep_channel.input(line)
330 self.kernel_manager.rep_channel.input(line)
331 self._readline(req['content']['prompt'], callback=callback)
331 self._readline(req['content']['prompt'], callback=callback)
332
332
333 def _handle_sub(self, omsg):
333 def _handle_sub(self, omsg):
334 if self._hidden:
334 if self._hidden:
335 return
335 return
336 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
336 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
337 if handler is not None:
337 if handler is not None:
338 handler(omsg)
338 handler(omsg)
339
339
340 def _handle_pyout(self, omsg):
340 def _handle_pyout(self, omsg):
341 self._append_plain_text(omsg['content']['data'] + '\n')
341 self._append_plain_text(omsg['content']['data'] + '\n')
342
342
343 def _handle_stream(self, omsg):
343 def _handle_stream(self, omsg):
344 self._append_plain_text(omsg['content']['data'])
344 self._append_plain_text(omsg['content']['data'])
345 self._control.moveCursor(QtGui.QTextCursor.End)
345 self._control.moveCursor(QtGui.QTextCursor.End)
346
346
347 def _handle_execute_reply(self, reply):
347 def _handle_execute_reply(self, reply):
348 if self._hidden:
348 if self._hidden:
349 return
349 return
350
350
351 # Make sure that all output from the SUB channel has been processed
351 # Make sure that all output from the SUB channel has been processed
352 # before writing a new prompt.
352 # before writing a new prompt.
353 self.kernel_manager.sub_channel.flush()
353 self.kernel_manager.sub_channel.flush()
354
354
355 # TODO: The reply now has the next_prompt and prompt_number keys.
355 status = reply['content']['status']
356 status = reply['content']['status']
356 if status == 'error':
357 if status == 'error':
357 self._handle_execute_error(reply)
358 self._handle_execute_error(reply)
358 elif status == 'aborted':
359 elif status == 'aborted':
359 text = "ERROR: ABORTED\n"
360 text = "ERROR: ABORTED\n"
360 self._append_plain_text(text)
361 self._append_plain_text(text)
361 self._hidden = True
362 self._hidden = True
362 self._show_interpreter_prompt()
363 self._show_interpreter_prompt()
363 self.executed.emit(reply)
364 self.executed.emit(reply)
364
365
365 def _handle_execute_error(self, reply):
366 def _handle_execute_error(self, reply):
366 content = reply['content']
367 content = reply['content']
367 traceback = ''.join(content['traceback'])
368 traceback = ''.join(content['traceback'])
368 self._append_plain_text(traceback)
369 self._append_plain_text(traceback)
369
370
370 def _handle_complete_reply(self, rep):
371 def _handle_complete_reply(self, rep):
371 cursor = self._get_cursor()
372 cursor = self._get_cursor()
372 if rep['parent_header']['msg_id'] == self._complete_id and \
373 if rep['parent_header']['msg_id'] == self._complete_id and \
373 cursor.position() == self._complete_pos:
374 cursor.position() == self._complete_pos:
374 text = '.'.join(self._get_context())
375 text = '.'.join(self._get_context())
375 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
376 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
376 self._complete_with_items(cursor, rep['content']['matches'])
377 self._complete_with_items(cursor, rep['content']['matches'])
377
378
378 def _handle_object_info_reply(self, rep):
379 def _handle_object_info_reply(self, rep):
379 cursor = self._get_cursor()
380 cursor = self._get_cursor()
380 if rep['parent_header']['msg_id'] == self._calltip_id and \
381 if rep['parent_header']['msg_id'] == self._calltip_id and \
381 cursor.position() == self._calltip_pos:
382 cursor.position() == self._calltip_pos:
382 doc = rep['content']['docstring']
383 doc = rep['content']['docstring']
383 if doc:
384 if doc:
384 self._call_tip_widget.show_docstring(doc)
385 self._call_tip_widget.show_docstring(doc)
@@ -1,369 +1,383 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 # Standard library imports.
17 # Standard library imports.
18 import __builtin__
18 import __builtin__
19 from code import CommandCompiler
20 import os
19 import os
21 import sys
20 import sys
22 import time
21 import time
23 import traceback
22 import traceback
24
23
25 # System library imports.
24 # System library imports.
26 import zmq
25 import zmq
27
26
28 # Local imports.
27 # Local imports.
29 from IPython.config.configurable import Configurable
28 from IPython.config.configurable import Configurable
30 from IPython.zmq.zmqshell import ZMQInteractiveShell
29 from IPython.zmq.zmqshell import ZMQInteractiveShell
31 from IPython.external.argparse import ArgumentParser
30 from IPython.external.argparse import ArgumentParser
32 from IPython.utils.traitlets import Instance
31 from IPython.utils.traitlets import Instance
33 from IPython.zmq.session import Session, Message
32 from IPython.zmq.session import Session, Message
34 from completer import KernelCompleter
35 from iostream import OutStream
33 from iostream import OutStream
36 from displayhook import DisplayHook
37 from exitpoller import ExitPollerUnix, ExitPollerWindows
34 from exitpoller import ExitPollerUnix, ExitPollerWindows
38
35
39 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
40 # Main kernel class
37 # Main kernel class
41 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
42
39
43 class Kernel(Configurable):
40 class Kernel(Configurable):
44
41
45 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
42 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
46 session = Instance('IPython.zmq.session.Session')
43 session = Instance('IPython.zmq.session.Session')
47 reply_socket = Instance('zmq.Socket')
44 reply_socket = Instance('zmq.Socket')
48 pub_socket = Instance('zmq.Socket')
45 pub_socket = Instance('zmq.Socket')
49 req_socket = Instance('zmq.Socket')
46 req_socket = Instance('zmq.Socket')
50
47
51 def __init__(self, **kwargs):
48 def __init__(self, **kwargs):
52 super(Kernel, self).__init__(**kwargs)
49 super(Kernel, self).__init__(**kwargs)
53
50
54 # Initialize the InteractiveShell subclass
51 # Initialize the InteractiveShell subclass
55 self.shell = ZMQInteractiveShell.instance()
52 self.shell = ZMQInteractiveShell.instance()
56 self.shell.displayhook.session = self.session
53 self.shell.displayhook.session = self.session
57 self.shell.displayhook.pub_socket = self.pub_socket
54 self.shell.displayhook.pub_socket = self.pub_socket
58
55
59 # Build dict of handlers for message types
56 # Build dict of handlers for message types
60 msg_types = [ 'execute_request', 'complete_request',
57 msg_types = [ 'execute_request', 'complete_request',
61 'object_info_request' ]
58 'object_info_request', 'prompt_request' ]
62 self.handlers = {}
59 self.handlers = {}
63 for msg_type in msg_types:
60 for msg_type in msg_types:
64 self.handlers[msg_type] = getattr(self, msg_type)
61 self.handlers[msg_type] = getattr(self, msg_type)
65
62
66 def abort_queue(self):
63 def abort_queue(self):
67 while True:
64 while True:
68 try:
65 try:
69 ident = self.reply_socket.recv(zmq.NOBLOCK)
66 ident = self.reply_socket.recv(zmq.NOBLOCK)
70 except zmq.ZMQError, e:
67 except zmq.ZMQError, e:
71 if e.errno == zmq.EAGAIN:
68 if e.errno == zmq.EAGAIN:
72 break
69 break
73 else:
70 else:
74 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
71 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
75 msg = self.reply_socket.recv_json()
72 msg = self.reply_socket.recv_json()
76 print>>sys.__stdout__, "Aborting:"
73 print>>sys.__stdout__, "Aborting:"
77 print>>sys.__stdout__, Message(msg)
74 print>>sys.__stdout__, Message(msg)
78 msg_type = msg['msg_type']
75 msg_type = msg['msg_type']
79 reply_type = msg_type.split('_')[0] + '_reply'
76 reply_type = msg_type.split('_')[0] + '_reply'
80 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
77 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
81 print>>sys.__stdout__, Message(reply_msg)
78 print>>sys.__stdout__, Message(reply_msg)
82 self.reply_socket.send(ident,zmq.SNDMORE)
79 self.reply_socket.send(ident,zmq.SNDMORE)
83 self.reply_socket.send_json(reply_msg)
80 self.reply_socket.send_json(reply_msg)
84 # We need to wait a bit for requests to come in. This can probably
81 # We need to wait a bit for requests to come in. This can probably
85 # be set shorter for true asynchronous clients.
82 # be set shorter for true asynchronous clients.
86 time.sleep(0.1)
83 time.sleep(0.1)
87
84
88 def execute_request(self, ident, parent):
85 def execute_request(self, ident, parent):
89 try:
86 try:
90 code = parent[u'content'][u'code']
87 code = parent[u'content'][u'code']
91 except:
88 except:
92 print>>sys.__stderr__, "Got bad msg: "
89 print>>sys.__stderr__, "Got bad msg: "
93 print>>sys.__stderr__, Message(parent)
90 print>>sys.__stderr__, Message(parent)
94 return
91 return
95 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
92 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
96 self.pub_socket.send_json(pyin_msg)
93 self.pub_socket.send_json(pyin_msg)
97
94
98 try:
95 try:
99 # Replace raw_input. Note that is not sufficient to replace
96 # Replace raw_input. Note that is not sufficient to replace
100 # raw_input in the user namespace.
97 # raw_input in the user namespace.
101 raw_input = lambda prompt='': self.raw_input(prompt, ident, parent)
98 raw_input = lambda prompt='': self.raw_input(prompt, ident, parent)
102 __builtin__.raw_input = raw_input
99 __builtin__.raw_input = raw_input
103
100
104 # Set the parent message of the display hook.
101 # Set the parent message of the display hook.
105 self.shell.displayhook.set_parent(parent)
102 self.shell.displayhook.set_parent(parent)
106
103
107 self.shell.runlines(code)
104 self.shell.runlines(code)
108 # exec comp_code in self.user_ns, self.user_ns
105 # exec comp_code in self.user_ns, self.user_ns
109 except:
106 except:
110 etype, evalue, tb = sys.exc_info()
107 etype, evalue, tb = sys.exc_info()
111 tb = traceback.format_exception(etype, evalue, tb)
108 tb = traceback.format_exception(etype, evalue, tb)
112 exc_content = {
109 exc_content = {
113 u'status' : u'error',
110 u'status' : u'error',
114 u'traceback' : tb,
111 u'traceback' : tb,
115 u'ename' : unicode(etype.__name__),
112 u'ename' : unicode(etype.__name__),
116 u'evalue' : unicode(evalue)
113 u'evalue' : unicode(evalue)
117 }
114 }
118 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
115 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
119 self.pub_socket.send_json(exc_msg)
116 self.pub_socket.send_json(exc_msg)
120 reply_content = exc_content
117 reply_content = exc_content
121 else:
118 else:
122 reply_content = {'status' : 'ok'}
119 reply_content = {'status' : 'ok'}
123
120
121 # Compute the prompt information
122 prompt_number = self.shell.displayhook.prompt_count
123 reply_content['prompt_number'] = prompt_number
124 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
125 next_prompt = {'prompt_string' : prompt_string,
126 'prompt_number' : prompt_number+1}
127 reply_content['next_prompt'] = next_prompt
128
124 # Flush output before sending the reply.
129 # Flush output before sending the reply.
125 sys.stderr.flush()
130 sys.stderr.flush()
126 sys.stdout.flush()
131 sys.stdout.flush()
127
132
128 # Send the reply.
133 # Send the reply.
129 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
134 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
130 print>>sys.__stdout__, Message(reply_msg)
135 print>>sys.__stdout__, Message(reply_msg)
131 self.reply_socket.send(ident, zmq.SNDMORE)
136 self.reply_socket.send(ident, zmq.SNDMORE)
132 self.reply_socket.send_json(reply_msg)
137 self.reply_socket.send_json(reply_msg)
133 if reply_msg['content']['status'] == u'error':
138 if reply_msg['content']['status'] == u'error':
134 self.abort_queue()
139 self.abort_queue()
135
140
136 def raw_input(self, prompt, ident, parent):
141 def raw_input(self, prompt, ident, parent):
137 # Flush output before making the request.
142 # Flush output before making the request.
138 sys.stderr.flush()
143 sys.stderr.flush()
139 sys.stdout.flush()
144 sys.stdout.flush()
140
145
141 # Send the input request.
146 # Send the input request.
142 content = dict(prompt=prompt)
147 content = dict(prompt=prompt)
143 msg = self.session.msg(u'input_request', content, parent)
148 msg = self.session.msg(u'input_request', content, parent)
144 self.req_socket.send_json(msg)
149 self.req_socket.send_json(msg)
145
150
146 # Await a response.
151 # Await a response.
147 reply = self.req_socket.recv_json()
152 reply = self.req_socket.recv_json()
148 try:
153 try:
149 value = reply['content']['value']
154 value = reply['content']['value']
150 except:
155 except:
151 print>>sys.__stderr__, "Got bad raw_input reply: "
156 print>>sys.__stderr__, "Got bad raw_input reply: "
152 print>>sys.__stderr__, Message(parent)
157 print>>sys.__stderr__, Message(parent)
153 value = ''
158 value = ''
154 return value
159 return value
155
160
156 def complete_request(self, ident, parent):
161 def complete_request(self, ident, parent):
157 matches = {'matches' : self.complete(parent),
162 matches = {'matches' : self.complete(parent),
158 'status' : 'ok'}
163 'status' : 'ok'}
159 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
164 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
160 matches, parent, ident)
165 matches, parent, ident)
161 print >> sys.__stdout__, completion_msg
166 print >> sys.__stdout__, completion_msg
162
167
163 def complete(self, msg):
168 def complete(self, msg):
164 return self.shell.complete(msg.content.line)
169 return self.shell.complete(msg.content.line)
165
170
166 def object_info_request(self, ident, parent):
171 def object_info_request(self, ident, parent):
167 context = parent['content']['oname'].split('.')
172 context = parent['content']['oname'].split('.')
168 object_info = self.object_info(context)
173 object_info = self.object_info(context)
169 msg = self.session.send(self.reply_socket, 'object_info_reply',
174 msg = self.session.send(self.reply_socket, 'object_info_reply',
170 object_info, parent, ident)
175 object_info, parent, ident)
171 print >> sys.__stdout__, msg
176 print >> sys.__stdout__, msg
172
177
173 def object_info(self, context):
178 def object_info(self, context):
174 symbol, leftover = self.symbol_from_context(context)
179 symbol, leftover = self.symbol_from_context(context)
175 if symbol is not None and not leftover:
180 if symbol is not None and not leftover:
176 doc = getattr(symbol, '__doc__', '')
181 doc = getattr(symbol, '__doc__', '')
177 else:
182 else:
178 doc = ''
183 doc = ''
179 object_info = dict(docstring = doc)
184 object_info = dict(docstring = doc)
180 return object_info
185 return object_info
181
186
182 def symbol_from_context(self, context):
187 def symbol_from_context(self, context):
183 if not context:
188 if not context:
184 return None, context
189 return None, context
185
190
186 base_symbol_string = context[0]
191 base_symbol_string = context[0]
187 symbol = self.shell.user_ns.get(base_symbol_string, None)
192 symbol = self.shell.user_ns.get(base_symbol_string, None)
188 if symbol is None:
193 if symbol is None:
189 symbol = __builtin__.__dict__.get(base_symbol_string, None)
194 symbol = __builtin__.__dict__.get(base_symbol_string, None)
190 if symbol is None:
195 if symbol is None:
191 return None, context
196 return None, context
192
197
193 context = context[1:]
198 context = context[1:]
194 for i, name in enumerate(context):
199 for i, name in enumerate(context):
195 new_symbol = getattr(symbol, name, None)
200 new_symbol = getattr(symbol, name, None)
196 if new_symbol is None:
201 if new_symbol is None:
197 return symbol, context[i:]
202 return symbol, context[i:]
198 else:
203 else:
199 symbol = new_symbol
204 symbol = new_symbol
200
205
201 return symbol, []
206 return symbol, []
202
207
208 def prompt_request(self, ident, parent):
209 prompt_number = self.shell.displayhook.prompt_count
210 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
211 content = {'prompt_string' : prompt_string,
212 'prompt_number' : prompt_number+1}
213 msg = self.session.send(self.reply_socket, 'prompt_reply',
214 content, parent, ident)
215 print >> sys.__stdout__, msg
216
203 def start(self):
217 def start(self):
204 while True:
218 while True:
205 ident = self.reply_socket.recv()
219 ident = self.reply_socket.recv()
206 assert self.reply_socket.rcvmore(), "Missing message part."
220 assert self.reply_socket.rcvmore(), "Missing message part."
207 msg = self.reply_socket.recv_json()
221 msg = self.reply_socket.recv_json()
208 omsg = Message(msg)
222 omsg = Message(msg)
209 print>>sys.__stdout__
223 print>>sys.__stdout__
210 print>>sys.__stdout__, omsg
224 print>>sys.__stdout__, omsg
211 handler = self.handlers.get(omsg.msg_type, None)
225 handler = self.handlers.get(omsg.msg_type, None)
212 if handler is None:
226 if handler is None:
213 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
227 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
214 else:
228 else:
215 handler(ident, omsg)
229 handler(ident, omsg)
216
230
217 #-----------------------------------------------------------------------------
231 #-----------------------------------------------------------------------------
218 # Kernel main and launch functions
232 # Kernel main and launch functions
219 #-----------------------------------------------------------------------------
233 #-----------------------------------------------------------------------------
220
234
221 def bind_port(socket, ip, port):
235 def bind_port(socket, ip, port):
222 """ Binds the specified ZMQ socket. If the port is less than zero, a random
236 """ Binds the specified ZMQ socket. If the port is less than zero, a random
223 port is chosen. Returns the port that was bound.
237 port is chosen. Returns the port that was bound.
224 """
238 """
225 connection = 'tcp://%s' % ip
239 connection = 'tcp://%s' % ip
226 if port <= 0:
240 if port <= 0:
227 port = socket.bind_to_random_port(connection)
241 port = socket.bind_to_random_port(connection)
228 else:
242 else:
229 connection += ':%i' % port
243 connection += ':%i' % port
230 socket.bind(connection)
244 socket.bind(connection)
231 return port
245 return port
232
246
233
247
234 def main():
248 def main():
235 """ Main entry point for launching a kernel.
249 """ Main entry point for launching a kernel.
236 """
250 """
237 # Parse command line arguments.
251 # Parse command line arguments.
238 parser = ArgumentParser()
252 parser = ArgumentParser()
239 parser.add_argument('--ip', type=str, default='127.0.0.1',
253 parser.add_argument('--ip', type=str, default='127.0.0.1',
240 help='set the kernel\'s IP address [default: local]')
254 help='set the kernel\'s IP address [default: local]')
241 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
255 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
242 help='set the XREP channel port [default: random]')
256 help='set the XREP channel port [default: random]')
243 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
257 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
244 help='set the PUB channel port [default: random]')
258 help='set the PUB channel port [default: random]')
245 parser.add_argument('--req', type=int, metavar='PORT', default=0,
259 parser.add_argument('--req', type=int, metavar='PORT', default=0,
246 help='set the REQ channel port [default: random]')
260 help='set the REQ channel port [default: random]')
247 if sys.platform == 'win32':
261 if sys.platform == 'win32':
248 parser.add_argument('--parent', type=int, metavar='HANDLE',
262 parser.add_argument('--parent', type=int, metavar='HANDLE',
249 default=0, help='kill this process if the process '
263 default=0, help='kill this process if the process '
250 'with HANDLE dies')
264 'with HANDLE dies')
251 else:
265 else:
252 parser.add_argument('--parent', action='store_true',
266 parser.add_argument('--parent', action='store_true',
253 help='kill this process if its parent dies')
267 help='kill this process if its parent dies')
254 namespace = parser.parse_args()
268 namespace = parser.parse_args()
255
269
256 # Create a context, a session, and the kernel sockets.
270 # Create a context, a session, and the kernel sockets.
257 print >>sys.__stdout__, "Starting the kernel..."
271 print >>sys.__stdout__, "Starting the kernel..."
258 context = zmq.Context()
272 context = zmq.Context()
259 session = Session(username=u'kernel')
273 session = Session(username=u'kernel')
260
274
261 reply_socket = context.socket(zmq.XREP)
275 reply_socket = context.socket(zmq.XREP)
262 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
276 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
263 print >>sys.__stdout__, "XREP Channel on port", xrep_port
277 print >>sys.__stdout__, "XREP Channel on port", xrep_port
264
278
265 pub_socket = context.socket(zmq.PUB)
279 pub_socket = context.socket(zmq.PUB)
266 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
280 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
267 print >>sys.__stdout__, "PUB Channel on port", pub_port
281 print >>sys.__stdout__, "PUB Channel on port", pub_port
268
282
269 req_socket = context.socket(zmq.XREQ)
283 req_socket = context.socket(zmq.XREQ)
270 req_port = bind_port(req_socket, namespace.ip, namespace.req)
284 req_port = bind_port(req_socket, namespace.ip, namespace.req)
271 print >>sys.__stdout__, "REQ Channel on port", req_port
285 print >>sys.__stdout__, "REQ Channel on port", req_port
272
286
273 # Redirect input streams. This needs to be done before the Kernel is done
287 # Redirect input streams. This needs to be done before the Kernel is done
274 # because currently the Kernel creates a ZMQInteractiveShell, which
288 # because currently the Kernel creates a ZMQInteractiveShell, which
275 # holds references to sys.stdout and sys.stderr.
289 # holds references to sys.stdout and sys.stderr.
276 sys.stdout = OutStream(session, pub_socket, u'stdout')
290 sys.stdout = OutStream(session, pub_socket, u'stdout')
277 sys.stderr = OutStream(session, pub_socket, u'stderr')
291 sys.stderr = OutStream(session, pub_socket, u'stderr')
278
292
279 # Create the kernel.
293 # Create the kernel.
280 kernel = Kernel(
294 kernel = Kernel(
281 session=session, reply_socket=reply_socket,
295 session=session, reply_socket=reply_socket,
282 pub_socket=pub_socket, req_socket=req_socket
296 pub_socket=pub_socket, req_socket=req_socket
283 )
297 )
284
298
285 # Configure this kernel/process to die on parent termination, if necessary.
299 # Configure this kernel/process to die on parent termination, if necessary.
286 if namespace.parent:
300 if namespace.parent:
287 if sys.platform == 'win32':
301 if sys.platform == 'win32':
288 poller = ExitPollerWindows(namespace.parent)
302 poller = ExitPollerWindows(namespace.parent)
289 else:
303 else:
290 poller = ExitPollerUnix()
304 poller = ExitPollerUnix()
291 poller.start()
305 poller.start()
292
306
293 # Start the kernel mainloop.
307 # Start the kernel mainloop.
294 kernel.start()
308 kernel.start()
295
309
296
310
297 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
311 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
298 """ Launches a localhost kernel, binding to the specified ports.
312 """ Launches a localhost kernel, binding to the specified ports.
299
313
300 Parameters
314 Parameters
301 ----------
315 ----------
302 xrep_port : int, optional
316 xrep_port : int, optional
303 The port to use for XREP channel.
317 The port to use for XREP channel.
304
318
305 pub_port : int, optional
319 pub_port : int, optional
306 The port to use for the SUB channel.
320 The port to use for the SUB channel.
307
321
308 req_port : int, optional
322 req_port : int, optional
309 The port to use for the REQ (raw input) channel.
323 The port to use for the REQ (raw input) channel.
310
324
311 independent : bool, optional (default False)
325 independent : bool, optional (default False)
312 If set, the kernel process is guaranteed to survive if this process
326 If set, the kernel process is guaranteed to survive if this process
313 dies. If not set, an effort is made to ensure that the kernel is killed
327 dies. If not set, an effort is made to ensure that the kernel is killed
314 when this process dies. Note that in this case it is still good practice
328 when this process dies. Note that in this case it is still good practice
315 to kill kernels manually before exiting.
329 to kill kernels manually before exiting.
316
330
317 Returns
331 Returns
318 -------
332 -------
319 A tuple of form:
333 A tuple of form:
320 (kernel_process, xrep_port, pub_port, req_port)
334 (kernel_process, xrep_port, pub_port, req_port)
321 where kernel_process is a Popen object and the ports are integers.
335 where kernel_process is a Popen object and the ports are integers.
322 """
336 """
323 import socket
337 import socket
324 from subprocess import Popen
338 from subprocess import Popen
325
339
326 # Find open ports as necessary.
340 # Find open ports as necessary.
327 ports = []
341 ports = []
328 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
342 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
329 for i in xrange(ports_needed):
343 for i in xrange(ports_needed):
330 sock = socket.socket()
344 sock = socket.socket()
331 sock.bind(('', 0))
345 sock.bind(('', 0))
332 ports.append(sock)
346 ports.append(sock)
333 for i, sock in enumerate(ports):
347 for i, sock in enumerate(ports):
334 port = sock.getsockname()[1]
348 port = sock.getsockname()[1]
335 sock.close()
349 sock.close()
336 ports[i] = port
350 ports[i] = port
337 if xrep_port <= 0:
351 if xrep_port <= 0:
338 xrep_port = ports.pop(0)
352 xrep_port = ports.pop(0)
339 if pub_port <= 0:
353 if pub_port <= 0:
340 pub_port = ports.pop(0)
354 pub_port = ports.pop(0)
341 if req_port <= 0:
355 if req_port <= 0:
342 req_port = ports.pop(0)
356 req_port = ports.pop(0)
343
357
344 # Spawn a kernel.
358 # Spawn a kernel.
345 command = 'from IPython.zmq.ipkernel import main; main()'
359 command = 'from IPython.zmq.ipkernel import main; main()'
346 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
360 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
347 '--pub', str(pub_port), '--req', str(req_port) ]
361 '--pub', str(pub_port), '--req', str(req_port) ]
348 if independent:
362 if independent:
349 if sys.platform == 'win32':
363 if sys.platform == 'win32':
350 proc = Popen(['start', '/b'] + arguments, shell=True)
364 proc = Popen(['start', '/b'] + arguments, shell=True)
351 else:
365 else:
352 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
366 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
353 else:
367 else:
354 if sys.platform == 'win32':
368 if sys.platform == 'win32':
355 from _subprocess import DuplicateHandle, GetCurrentProcess, \
369 from _subprocess import DuplicateHandle, GetCurrentProcess, \
356 DUPLICATE_SAME_ACCESS
370 DUPLICATE_SAME_ACCESS
357 pid = GetCurrentProcess()
371 pid = GetCurrentProcess()
358 handle = DuplicateHandle(pid, pid, pid, 0,
372 handle = DuplicateHandle(pid, pid, pid, 0,
359 True, # Inheritable by new processes.
373 True, # Inheritable by new processes.
360 DUPLICATE_SAME_ACCESS)
374 DUPLICATE_SAME_ACCESS)
361 proc = Popen(arguments + ['--parent', str(int(handle))])
375 proc = Popen(arguments + ['--parent', str(int(handle))])
362 else:
376 else:
363 proc = Popen(arguments + ['--parent'])
377 proc = Popen(arguments + ['--parent'])
364
378
365 return proc, xrep_port, pub_port, req_port
379 return proc, xrep_port, pub_port, req_port
366
380
367
381
368 if __name__ == '__main__':
382 if __name__ == '__main__':
369 main()
383 main()
@@ -1,72 +1,72 b''
1 import sys
1 import sys
2 from subprocess import Popen, PIPE
2 from subprocess import Popen, PIPE
3
3
4 from IPython.core.interactiveshell import (
4 from IPython.core.interactiveshell import (
5 InteractiveShell, InteractiveShellABC
5 InteractiveShell, InteractiveShellABC
6 )
6 )
7 from IPython.core.displayhook import DisplayHook
7 from IPython.core.displayhook import DisplayHook
8 from IPython.utils.traitlets import Instance, Type, Dict
8 from IPython.utils.traitlets import Instance, Type, Dict
9 from IPython.zmq.session import extract_header
9 from IPython.zmq.session import extract_header
10
10
11
11
12 class ZMQDisplayTrap(DisplayHook):
12 class ZMQDisplayHook(DisplayHook):
13
13
14 session = Instance('IPython.zmq.session.Session')
14 session = Instance('IPython.zmq.session.Session')
15 pub_socket = Instance('zmq.Socket')
15 pub_socket = Instance('zmq.Socket')
16 parent_header = Dict({})
16 parent_header = Dict({})
17
17
18 def set_parent(self, parent):
18 def set_parent(self, parent):
19 """Set the parent for outbound messages."""
19 """Set the parent for outbound messages."""
20 self.parent_header = extract_header(parent)
20 self.parent_header = extract_header(parent)
21
21
22 def start_displayhook(self):
22 def start_displayhook(self):
23 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
23 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
24
24
25 def write_output_prompt(self):
25 def write_output_prompt(self):
26 """Write the output prompt."""
26 """Write the output prompt."""
27 if self.do_full_cache:
27 if self.do_full_cache:
28 self.msg['content']['output_sep'] = self.output_sep
28 self.msg['content']['output_sep'] = self.output_sep
29 self.msg['content']['prompt_string'] = str(self.prompt_out)
29 self.msg['content']['prompt_string'] = str(self.prompt_out)
30 self.msg['content']['prompt_number'] = self.prompt_count
30 self.msg['content']['prompt_number'] = self.prompt_count
31 self.msg['content']['output_sep2'] = self.output_sep2
31 self.msg['content']['output_sep2'] = self.output_sep2
32
32
33 def write_result_repr(self, result_repr):
33 def write_result_repr(self, result_repr):
34 self.msg['content']['data'] = result_repr
34 self.msg['content']['data'] = result_repr
35
35
36 def finish_displayhook(self):
36 def finish_displayhook(self):
37 """Finish up all displayhook activities."""
37 """Finish up all displayhook activities."""
38 self.pub_socket.send_json(self.msg)
38 self.pub_socket.send_json(self.msg)
39 self.msg = None
39 self.msg = None
40
40
41
41
42 class ZMQInteractiveShell(InteractiveShell):
42 class ZMQInteractiveShell(InteractiveShell):
43 """A subclass of InteractiveShell for ZMQ."""
43 """A subclass of InteractiveShell for ZMQ."""
44
44
45 displayhook_class = Type(ZMQDisplayTrap)
45 displayhook_class = Type(ZMQDisplayHook)
46
46
47 def system(self, cmd):
47 def system(self, cmd):
48 cmd = self.var_expand(cmd, depth=2)
48 cmd = self.var_expand(cmd, depth=2)
49 sys.stdout.flush()
49 sys.stdout.flush()
50 sys.stderr.flush()
50 sys.stderr.flush()
51 p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
51 p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
52 for line in p.stdout.read().split('\n'):
52 for line in p.stdout.read().split('\n'):
53 if len(line) > 0:
53 if len(line) > 0:
54 print line
54 print line
55 for line in p.stderr.read().split('\n'):
55 for line in p.stderr.read().split('\n'):
56 if len(line) > 0:
56 if len(line) > 0:
57 print line
57 print line
58 p.wait()
58 p.wait()
59
59
60 def init_io(self):
60 def init_io(self):
61 # This will just use sys.stdout and sys.stderr. If you want to
61 # This will just use sys.stdout and sys.stderr. If you want to
62 # override sys.stdout and sys.stderr themselves, you need to do that
62 # override sys.stdout and sys.stderr themselves, you need to do that
63 # *before* instantiating this class, because Term holds onto
63 # *before* instantiating this class, because Term holds onto
64 # references to the underlying streams.
64 # references to the underlying streams.
65 import IPython.utils.io
65 import IPython.utils.io
66 Term = IPython.utils.io.IOTerm()
66 Term = IPython.utils.io.IOTerm()
67 IPython.utils.io.Term = Term
67 IPython.utils.io.Term = Term
68
68
69 InteractiveShellABC.register(ZMQInteractiveShell)
69 InteractiveShellABC.register(ZMQInteractiveShell)
70
70
71
71
72
72
General Comments 0
You need to be logged in to leave comments. Login now