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