##// END OF EJS Templates
Take in account remarks by Fernando on code review
Gael Varoquaux -
Show More
@@ -1,367 +1,372
1 1 """
2 2 Base front end class for all line-oriented frontends, rather than
3 3 block-oriented.
4 4
5 5 Currently this focuses on synchronous frontends.
6 6 """
7 7 __docformat__ = "restructuredtext en"
8 8
9 9 #-------------------------------------------------------------------------------
10 10 # Copyright (C) 2008 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-------------------------------------------------------------------------------
15 15
16 16 #-------------------------------------------------------------------------------
17 17 # Imports
18 18 #-------------------------------------------------------------------------------
19 19 import re
20 20
21 21 import sys
22 22 import codeop
23 23
24 24 from frontendbase import FrontEndBase
25 25 from IPython.kernel.core.interpreter import Interpreter
26 26
27 27 def common_prefix(strings):
28 28 """ Given a list of strings, return the common prefix between all
29 29 these strings.
30 30 """
31 31 ref = strings[0]
32 32 prefix = ''
33 33 for size in range(len(ref)):
34 34 test_prefix = ref[:size+1]
35 35 for string in strings[1:]:
36 36 if not string.startswith(test_prefix):
37 37 return prefix
38 38 prefix = test_prefix
39 39
40 40 return prefix
41 41
42 42 #-------------------------------------------------------------------------------
43 43 # Base class for the line-oriented front ends
44 44 #-------------------------------------------------------------------------------
45 45 class LineFrontEndBase(FrontEndBase):
46 46 """ Concrete implementation of the FrontEndBase class. This is meant
47 47 to be the base class behind all the frontend that are line-oriented,
48 48 rather than block-oriented.
49 49 """
50 50
51 51 # We need to keep the prompt number, to be able to increment
52 52 # it when there is an exception.
53 53 prompt_number = 1
54 54
55 55 # We keep a reference to the last result: it helps testing and
56 56 # programatic control of the frontend.
57 57 last_result = dict(number=0)
58 58
59 59 # The last prompt displayed. Useful for continuation prompts.
60 60 last_prompt = ''
61 61
62 62 # The input buffer being edited
63 63 input_buffer = ''
64 64
65 65 # Set to true for debug output
66 66 debug = False
67 67
68 68 # A banner to print at startup
69 69 banner = None
70 70
71 71 #--------------------------------------------------------------------------
72 72 # FrontEndBase interface
73 73 #--------------------------------------------------------------------------
74 74
75 75 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
76 76 if shell is None:
77 77 shell = Interpreter()
78 78 FrontEndBase.__init__(self, shell=shell, history=history)
79 79
80 80 if banner is not None:
81 81 self.banner = banner
82 82
83 83 def start(self):
84 84 """ Put the frontend in a state where it is ready for user
85 85 interaction.
86 86 """
87 87 if self.banner is not None:
88 88 self.write(self.banner, refresh=False)
89 89
90 90 self.new_prompt(self.input_prompt_template.substitute(number=1))
91 91
92 92
93 93 def complete(self, line):
94 94 """Complete line in engine's user_ns
95 95
96 96 Parameters
97 97 ----------
98 98 line : string
99 99
100 100 Result
101 101 ------
102 102 The replacement for the line and the list of possible completions.
103 103 """
104 104 completions = self.shell.complete(line)
105 105 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
106 106 if completions:
107 107 prefix = common_prefix(completions)
108 108 residual = complete_sep.split(line)[:-1]
109 109 line = line[:-len(residual)] + prefix
110 110 return line, completions
111 111
112 112
113 113 def render_result(self, result):
114 114 """ Frontend-specific rendering of the result of a calculation
115 115 that has been sent to an engine.
116 116 """
117 117 if 'stdout' in result and result['stdout']:
118 118 self.write('\n' + result['stdout'])
119 119 if 'display' in result and result['display']:
120 120 self.write("%s%s\n" % (
121 121 self.output_prompt_template.substitute(
122 122 number=result['number']),
123 123 result['display']['pprint']
124 124 ) )
125 125
126 126
127 127 def render_error(self, failure):
128 128 """ Frontend-specific rendering of error.
129 129 """
130 130 self.write('\n\n'+str(failure)+'\n\n')
131 131 return failure
132 132
133 133
134 134 def is_complete(self, string):
135 135 """ Check if a string forms a complete, executable set of
136 136 commands.
137 137
138 138 For the line-oriented frontend, multi-line code is not executed
139 139 as soon as it is complete: the users has to enter two line
140 140 returns.
141 141 """
142 142 if string in ('', '\n'):
143 143 # Prefiltering, eg through ipython0, may return an empty
144 144 # string although some operations have been accomplished. We
145 145 # thus want to consider an empty string as a complete
146 146 # statement.
147 147 return True
148 148 elif ( len(self.input_buffer.split('\n'))>2
149 149 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
150 150 return False
151 151 else:
152 152 self.capture_output()
153 153 try:
154 154 # Add line returns here, to make sure that the statement is
155 155 # complete (except if '\' was used).
156 156 # This should probably be done in a different place (like
157 157 # maybe 'prefilter_input' method? For now, this works.
158 158 clean_string = string.rstrip('\n')
159 159 if not clean_string.endswith('\\'): clean_string +='\n\n'
160 160 is_complete = codeop.compile_command(clean_string,
161 161 "<string>", "exec")
162 162 self.release_output()
163 163 except Exception, e:
164 164 # XXX: Hack: return True so that the
165 165 # code gets executed and the error captured.
166 166 is_complete = True
167 167 return is_complete
168 168
169 169
170 170 def write(self, string, refresh=True):
171 171 """ Write some characters to the display.
172 172
173 173 Subclass should overide this method.
174 174
175 175 The refresh keyword argument is used in frontends with an
176 176 event loop, to choose whether the write should trigget an UI
177 177 refresh, and thus be syncrhonous, or not.
178 178 """
179 179 print >>sys.__stderr__, string
180 180
181 181
182 182 def execute(self, python_string, raw_string=None):
183 183 """ Stores the raw_string in the history, and sends the
184 184 python string to the interpreter.
185 185 """
186 186 if raw_string is None:
187 187 raw_string = python_string
188 188 # Create a false result, in case there is an exception
189 189 self.last_result = dict(number=self.prompt_number)
190 190
191 191 try:
192 192 try:
193 193 self.history.input_cache[-1] = raw_string.rstrip()
194 194 result = self.shell.execute(python_string)
195 195 self.last_result = result
196 196 self.render_result(result)
197 197 except:
198 198 self.show_traceback()
199 199 finally:
200 200 self.after_execute()
201 201
202 202
203 203 #--------------------------------------------------------------------------
204 204 # LineFrontEndBase interface
205 205 #--------------------------------------------------------------------------
206 206
207 207 def prefilter_input(self, string):
208 208 """ Prefilter the input to turn it in valid python.
209 209 """
210 210 string = string.replace('\r\n', '\n')
211 211 string = string.replace('\t', 4*' ')
212 212 # Clean the trailing whitespace
213 213 string = '\n'.join(l.rstrip() for l in string.split('\n'))
214 214 return string
215 215
216 216
217 217 def after_execute(self):
218 218 """ All the operations required after an execution to put the
219 219 terminal back in a shape where it is usable.
220 220 """
221 221 self.prompt_number += 1
222 222 self.new_prompt(self.input_prompt_template.substitute(
223 223 number=(self.last_result['number'] + 1)))
224 224 # Start a new empty history entry
225 225 self._add_history(None, '')
226 226 self.history_cursor = len(self.history.input_cache) - 1
227 227
228 228
229 229 def complete_current_input(self):
230 230 """ Do code completion on current line.
231 231 """
232 232 if self.debug:
233 233 print >>sys.__stdout__, "complete_current_input",
234 234 line = self.input_buffer
235 235 new_line, completions = self.complete(line)
236 236 if len(completions)>1:
237 237 self.write_completion(completions, new_line=new_line)
238 238 elif not line == new_line:
239 239 self.input_buffer = new_line
240 240 if self.debug:
241 241 print >>sys.__stdout__, 'line', line
242 242 print >>sys.__stdout__, 'new_line', new_line
243 243 print >>sys.__stdout__, completions
244 244
245 245
246 246 def get_line_width(self):
247 247 """ Return the width of the line in characters.
248 248 """
249 249 return 80
250 250
251 251
252 252 def write_completion(self, possibilities, new_line=None):
253 253 """ Write the list of possible completions.
254 254
255 255 new_line is the completed input line that should be displayed
256 256 after the completion are writen. If None, the input_buffer
257 257 before the completion is used.
258 258 """
259 259 if new_line is None:
260 260 new_line = self.input_buffer
261 261
262 262 self.write('\n')
263 263 max_len = len(max(possibilities, key=len)) + 1
264 264
265 265 # Now we check how much symbol we can put on a line...
266 266 chars_per_line = self.get_line_width()
267 267 symbols_per_line = max(1, chars_per_line/max_len)
268 268
269 269 pos = 1
270 270 completion_string = []
271 271 for symbol in possibilities:
272 272 if pos < symbols_per_line:
273 273 completion_string.append(symbol.ljust(max_len))
274 274 pos += 1
275 275 else:
276 276 completion_string.append(symbol.rstrip() + '\n')
277 277 pos = 1
278 278 self.write(''.join(completion_string))
279 279 self.new_prompt(self.input_prompt_template.substitute(
280 280 number=self.last_result['number'] + 1))
281 281 self.input_buffer = new_line
282 282
283 283
284 284 def new_prompt(self, prompt):
285 285 """ Prints a prompt and starts a new editing buffer.
286 286
287 287 Subclasses should use this method to make sure that the
288 288 terminal is put in a state favorable for a new line
289 289 input.
290 290 """
291 291 self.input_buffer = ''
292 292 self.write(prompt)
293 293
294 294
295 295 def continuation_prompt(self):
296 296 """Returns the current continuation prompt.
297 297 """
298 298 return ("."*(len(self.last_prompt)-2) + ': ')
299 299
300 300
301 301 def execute_command(self, command, hidden=False):
302 302 """ Execute a command, not only in the model, but also in the
303 303 view, if any.
304 304 """
305 305 return self.shell.execute(command)
306 306
307 307 #--------------------------------------------------------------------------
308 308 # Private API
309 309 #--------------------------------------------------------------------------
310 310
311 311 def _on_enter(self, new_line_pos=0):
312 312 """ Called when the return key is pressed in a line editing
313 313 buffer.
314 314
315 315 Parameters
316 316 ----------
317 317 new_line_pos : integer, optional
318 318 Position of the new line to add, starting from the
319 319 end (0 adds a new line after the last line, -1 before
320 320 the last line...)
321 321
322 322 Returns
323 323 -------
324 324 True if execution is triggered
325 325 """
326 326 current_buffer = self.input_buffer
327 327 # XXX: This string replace is ugly, but there should be no way it
328 328 # fails.
329 329 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
330 330 '', current_buffer).replace('\n' + self.continuation_prompt(),
331 331 '\n')
332 332 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
333 333 if self.is_complete(cleaned_buffer):
334 334 self.execute(cleaned_buffer, raw_string=current_buffer)
335 335 return True
336 336 else:
337 # Start a new line.
337 338 new_line_pos = -new_line_pos
338 339 lines = current_buffer.split('\n')[:-1]
339 340 prompt_less_lines = prompt_less_buffer.split('\n')
341 # Create the new line, with the continuation prompt, and the
342 # same amount of indent than the line above it.
340 343 new_line = self.continuation_prompt() + \
341 344 self._get_indent_string('\n'.join(
342 345 prompt_less_lines[:new_line_pos-1]))
343 346 if len(lines) == 1:
347 # We are starting a first continuation line. Indent it.
344 348 new_line += '\t'
345 349 elif current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
350 # The last line ends with ":", autoindent the new line.
346 351 new_line += '\t'
347 352
348 353 if new_line_pos == 0:
349 354 lines.append(new_line)
350 355 else:
351 356 lines.insert(new_line_pos, new_line)
352 357 self.input_buffer = '\n'.join(lines)
353 358
354 359
355 360 def _get_indent_string(self, string):
356 361 """ Return the string of whitespace that prefixes a line. Used to
357 362 add the right amount of indendation when creating a new line.
358 363 """
359 364 string = string.replace('\t', ' '*4)
360 365 string = string.split('\n')[-1]
361 366 indent_chars = len(string) - len(string.lstrip())
362 367 indent_string = '\t'*(indent_chars // 4) + \
363 368 ' '*(indent_chars % 4)
364 369
365 370 return indent_string
366 371
367 372
1 NO CONTENT: file renamed from IPython/frontend/_process/__init__.py to IPython/frontend/process/__init__.py
1 NO CONTENT: file renamed from IPython/frontend/_process/killableprocess.py to IPython/frontend/process/killableprocess.py
1 NO CONTENT: file renamed from IPython/frontend/_process/pipedprocess.py to IPython/frontend/process/pipedprocess.py
1 NO CONTENT: file renamed from IPython/frontend/_process/winprocess.py to IPython/frontend/process/winprocess.py
@@ -1,38 +1,37
1 1 # encoding: utf-8
2 2 """
3 3 Test the LineFrontEnd
4 4 """
5 5
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is
12 12 # in the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 from IPython.frontend.linefrontendbase import LineFrontEndBase
16 16 from copy import deepcopy
17 import nose.tools as nt
17 18
18 19 class ConcreteLineFrontEnd(LineFrontEndBase):
19 20 """ A concrete class to test the LineFrontEndBase.
20 21 """
21 22 def capture_output(self):
22 23 pass
23 24
24 25 def release_output(self):
25 26 pass
26 27
27 28
28 29 def test_is_complete():
29 30 """ Tests line completion heuristic.
30 31 """
31 32 frontend = ConcreteLineFrontEnd()
32 assert not frontend.is_complete('for x in \\')
33 assert not frontend.is_complete('for x in (1, ):')
34 assert frontend.is_complete('for x in (1, ):\n pass')
33 yield nt.assert_true, not frontend.is_complete('for x in \\')
34 yield nt.assert_true, not frontend.is_complete('for x in (1, ):')
35 yield nt.assert_true, frontend.is_complete('for x in (1, ):\n pass')
35 36
36 37
37 if __name__ == '__main__':
38 test_is_complete()
@@ -1,244 +1,244
1 1 # encoding: utf-8
2 2 """
3 3 Test process execution and IO redirection.
4 4 """
5 5
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is
12 12 # in the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 from cStringIO import StringIO
16 16 import string
17 17
18 18 from nose.tools import assert_equal
19 19
20 20 from IPython.ipapi import get as get_ipython0
21 21 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
22 22 from copy import copy, deepcopy
23 23
24 24 def safe_deepcopy(d):
25 25 """ Deep copy every key of the given dict, when possible. Elsewhere
26 26 do a copy.
27 27 """
28 28 copied_d = dict()
29 29 for key, value in d.iteritems():
30 30 try:
31 31 copied_d[key] = deepcopy(value)
32 32 except:
33 33 try:
34 34 copied_d[key] = copy(value)
35 35 except:
36 36 copied_d[key] = value
37 37 return copied_d
38 38
39 39
40 40 class TestPrefilterFrontEnd(PrefilterFrontEnd):
41 41
42 42 input_prompt_template = string.Template('')
43 43 output_prompt_template = string.Template('')
44 44 banner = ''
45 45
46 46 def __init__(self):
47 47 self.out = StringIO()
48 48 PrefilterFrontEnd.__init__(self)
49 49 # Some more code for isolation (yeah, crazy)
50 50 self._on_enter()
51 51 self.out.flush()
52 52 self.out.reset()
53 53 self.out.truncate()
54 54
55 55 def write(self, string, *args, **kwargs):
56 56 self.out.write(string)
57 57
58 58 def _on_enter(self):
59 59 self.input_buffer += '\n'
60 60 PrefilterFrontEnd._on_enter(self)
61 61
62 62
63 63 def isolate_ipython0(func):
64 64 """ Decorator to isolate execution that involves an iptyhon0.
65 65
66 66 Notes
67 67 ------
68 68
69 69 Apply only to functions with no arguments. Nose skips functions
70 70 with arguments.
71 71 """
72 72 def my_func():
73 73 iplib = get_ipython0()
74 74 if iplib is None:
75 75 return func()
76 76 ipython0 = iplib.IP
77 77 global_ns = safe_deepcopy(ipython0.user_global_ns)
78 78 user_ns = safe_deepcopy(ipython0.user_ns)
79 79 try:
80 80 out = func()
81 81 finally:
82 82 ipython0.user_ns = user_ns
83 83 ipython0.user_global_ns = global_ns
84 84 # Undo the hack at creation of PrefilterFrontEnd
85 85 from IPython import iplib
86 86 iplib.InteractiveShell.isthreaded = False
87 87 return out
88 88
89 89 my_func.__name__ = func.__name__
90 90 return my_func
91 91
92 92
93 93 @isolate_ipython0
94 94 def test_execution():
95 95 """ Test execution of a command.
96 96 """
97 97 f = TestPrefilterFrontEnd()
98 98 f.input_buffer = 'print 1'
99 99 f._on_enter()
100 100 out_value = f.out.getvalue()
101 101 assert_equal(out_value, '1\n')
102 102
103 103
104 104 @isolate_ipython0
105 105 def test_multiline():
106 106 """ Test execution of a multiline command.
107 107 """
108 108 f = TestPrefilterFrontEnd()
109 109 f.input_buffer = 'if True:'
110 110 f._on_enter()
111 111 f.input_buffer += 'print 1'
112 112 f._on_enter()
113 113 out_value = f.out.getvalue()
114 assert_equal(out_value, '')
114 yield assert_equal, out_value, ''
115 115 f._on_enter()
116 116 out_value = f.out.getvalue()
117 assert_equal(out_value, '1\n')
117 yield assert_equal, out_value, '1\n'
118 118 f = TestPrefilterFrontEnd()
119 119 f.input_buffer='(1 +'
120 120 f._on_enter()
121 121 f.input_buffer += '0)'
122 122 f._on_enter()
123 123 out_value = f.out.getvalue()
124 assert_equal(out_value, '')
124 yield assert_equal, out_value, ''
125 125 f._on_enter()
126 126 out_value = f.out.getvalue()
127 assert_equal(out_value, '1\n')
127 yield assert_equal, out_value, '1\n'
128 128
129 129
130 130 @isolate_ipython0
131 131 def test_capture():
132 132 """ Test the capture of output in different channels.
133 133 """
134 134 # Test on the OS-level stdout, stderr.
135 135 f = TestPrefilterFrontEnd()
136 136 f.input_buffer = \
137 137 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
138 138 f._on_enter()
139 139 out_value = f.out.getvalue()
140 assert_equal(out_value, '1')
140 yield assert_equal, out_value, '1'
141 141 f = TestPrefilterFrontEnd()
142 142 f.input_buffer = \
143 143 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
144 144 f._on_enter()
145 145 out_value = f.out.getvalue()
146 assert_equal(out_value, '1')
146 yield assert_equal, out_value, '1'
147 147
148 148
149 149 @isolate_ipython0
150 150 def test_magic():
151 151 """ Test the magic expansion and history.
152 152
153 153 This test is fairly fragile and will break when magics change.
154 154 """
155 155 f = TestPrefilterFrontEnd()
156 156 f.input_buffer += '%who'
157 157 f._on_enter()
158 158 out_value = f.out.getvalue()
159 159 assert_equal(out_value, 'Interactive namespace is empty.\n')
160 160
161 161
162 162 @isolate_ipython0
163 163 def test_help():
164 164 """ Test object inspection.
165 165 """
166 166 f = TestPrefilterFrontEnd()
167 167 f.input_buffer += "def f():"
168 168 f._on_enter()
169 169 f.input_buffer += "'foobar'"
170 170 f._on_enter()
171 171 f.input_buffer += "pass"
172 172 f._on_enter()
173 173 f._on_enter()
174 174 f.input_buffer += "f?"
175 175 f._on_enter()
176 176 assert 'traceback' not in f.last_result
177 177 ## XXX: ipython doctest magic breaks this. I have no clue why
178 178 #out_value = f.out.getvalue()
179 179 #assert out_value.split()[-1] == 'foobar'
180 180
181 181
182 182 @isolate_ipython0
183 183 def test_completion_simple():
184 184 """ Test command-line completion on trivial examples.
185 185 """
186 186 f = TestPrefilterFrontEnd()
187 187 f.input_buffer = 'zzza = 1'
188 188 f._on_enter()
189 189 f.input_buffer = 'zzzb = 2'
190 190 f._on_enter()
191 191 f.input_buffer = 'zz'
192 192 f.complete_current_input()
193 193 out_value = f.out.getvalue()
194 assert_equal(out_value, '\nzzza zzzb ')
195 assert_equal(f.input_buffer, 'zzz')
194 yield assert_equal, out_value, '\nzzza zzzb '
195 yield assert_equal, f.input_buffer, 'zzz'
196 196
197 197
198 198 @isolate_ipython0
199 199 def test_completion_parenthesis():
200 200 """ Test command-line completion when a parenthesis is open.
201 201 """
202 202 f = TestPrefilterFrontEnd()
203 203 f.input_buffer = 'zzza = 1'
204 204 f._on_enter()
205 205 f.input_buffer = 'zzzb = 2'
206 206 f._on_enter()
207 207 f.input_buffer = 'map(zz'
208 208 f.complete_current_input()
209 209 out_value = f.out.getvalue()
210 assert_equal(out_value, '\nzzza zzzb ')
211 assert_equal(f.input_buffer, 'map(zzz')
210 yield assert_equal, out_value, '\nzzza zzzb '
211 yield assert_equal, f.input_buffer, 'map(zzz'
212 212
213 213
214 214 @isolate_ipython0
215 215 def test_completion_indexing():
216 216 """ Test command-line completion when indexing on objects.
217 217 """
218 218 f = TestPrefilterFrontEnd()
219 219 f.input_buffer = 'a = [0]'
220 220 f._on_enter()
221 221 f.input_buffer = 'a[0].'
222 222 f.complete_current_input()
223 223 assert_equal(f.input_buffer, 'a[0].__')
224 224
225 225
226 226 @isolate_ipython0
227 227 def test_completion_equal():
228 228 """ Test command-line completion when the delimiter is "=", not " ".
229 229 """
230 230 f = TestPrefilterFrontEnd()
231 231 f.input_buffer = 'a=1.'
232 232 f.complete_current_input()
233 233 assert_equal(f.input_buffer, 'a=1.__')
234 234
235 235
236 236
237 237 if __name__ == '__main__':
238 238 test_magic()
239 239 test_help()
240 240 test_execution()
241 241 test_multiline()
242 242 test_capture()
243 243 test_completion_simple()
244 244 test_completion_complex()
@@ -1,67 +1,67
1 1 # encoding: utf-8
2 2 """
3 3 Test process execution and IO redirection.
4 4 """
5 5
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is
12 12 # in the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 from cStringIO import StringIO
16 16 from time import sleep
17 17 import sys
18 18
19 from IPython.frontend._process import PipedProcess
19 from IPython.frontend.process import PipedProcess
20 20 from IPython.testing import decorators as testdec
21 21
22 22
23 23 def test_capture_out():
24 24 """ A simple test to see if we can execute a process and get the output.
25 25 """
26 26 s = StringIO()
27 27 p = PipedProcess('echo 1', out_callback=s.write, )
28 28 p.start()
29 29 p.join()
30 30 result = s.getvalue().rstrip()
31 31 assert result == '1'
32 32
33 33
34 34 def test_io():
35 35 """ Checks that we can send characters on stdin to the process.
36 36 """
37 37 s = StringIO()
38 38 p = PipedProcess(sys.executable + ' -c "a = raw_input(); print a"',
39 39 out_callback=s.write, )
40 40 p.start()
41 41 test_string = '12345\n'
42 42 while not hasattr(p, 'process'):
43 43 sleep(0.1)
44 44 p.process.stdin.write(test_string)
45 45 p.join()
46 46 result = s.getvalue()
47 47 assert result == test_string
48 48
49 49
50 50 def test_kill():
51 51 """ Check that we can kill a process, and its subprocess.
52 52 """
53 53 s = StringIO()
54 54 p = PipedProcess(sys.executable + ' -c "a = raw_input();"',
55 55 out_callback=s.write, )
56 56 p.start()
57 57 while not hasattr(p, 'process'):
58 58 sleep(0.1)
59 59 p.process.kill()
60 60 assert p.process.poll() is not None
61 61
62 62
63 63 if __name__ == '__main__':
64 64 test_capture_out()
65 65 test_io()
66 66 test_kill()
67 67
@@ -1,656 +1,625
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in 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 wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26 import time
27 27 import sys
28 28 import string
29 29
30 30 LINESEP = '\n'
31 31 if sys.platform == 'win32':
32 32 LINESEP = '\n\r'
33 33
34 34 import re
35 35
36 36 # FIXME: Need to provide an API for non user-generated display on the
37 37 # screen: this should not be editable by the user.
38 38 #-------------------------------------------------------------------------------
39 39 # Constants
40 40 #-------------------------------------------------------------------------------
41 41 _COMPLETE_BUFFER_MARKER = 31
42 42 _ERROR_MARKER = 30
43 43 _INPUT_MARKER = 29
44 44
45 45 _DEFAULT_SIZE = 10
46 46 if sys.platform == 'darwin':
47 47 _DEFAULT_SIZE = 12
48 48
49 49 _DEFAULT_STYLE = {
50 50 #background definition
51 51 'default' : 'size:%d' % _DEFAULT_SIZE,
52 52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
53 53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
54 54
55 55 # Edge column: a number of None
56 56 'edge_column' : -1,
57 57
58 58 # properties for the various Python lexer styles
59 59 'comment' : 'fore:#007F00',
60 60 'number' : 'fore:#007F7F',
61 61 'string' : 'fore:#7F007F,italic',
62 62 'char' : 'fore:#7F007F,italic',
63 63 'keyword' : 'fore:#00007F,bold',
64 64 'triple' : 'fore:#7F0000',
65 65 'tripledouble' : 'fore:#7F0000',
66 66 'class' : 'fore:#0000FF,bold,underline',
67 67 'def' : 'fore:#007F7F,bold',
68 'operator' : 'bold'
68 'operator' : 'bold',
69
70 # Default colors
71 'trace' : '#FAFAF1', # Nice green
72 'stdout' : '#FDFFD3', # Nice yellow
73 'stderr' : '#FFF1F1', # Nice red
74
75 # Default scintilla settings
76 'antialiasing' : True,
77 'carret_color' : 'BLACK',
78 'background_color' :'WHITE',
79
80 #prompt definition
81 'prompt_in1' : \
82 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02',
83
84 'prompt_out': \
85 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02',
69 86 }
70 87
71 88 # new style numbers
72 89 _STDOUT_STYLE = 15
73 90 _STDERR_STYLE = 16
74 91 _TRACE_STYLE = 17
75 92
76 93
77 94 # system colors
78 95 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
79 96
80 97 # Translation table from ANSI escape sequences to color.
81 98 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
82 99 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
83 100 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
84 101 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
85 102 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
86 103 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
87 104 '1;34': [12, 'LIGHT BLUE'], '1;35':
88 105 [13, 'MEDIUM VIOLET RED'],
89 106 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
90 107
108 # XXX: Maybe one day we should factor this code with ColorANSI. Right now
109 # ColorANSI is hard to reuse and makes our code more complex.
110
91 111 #we define platform specific fonts
92 112 if wx.Platform == '__WXMSW__':
93 113 FACES = { 'times': 'Times New Roman',
94 114 'mono' : 'Courier New',
95 115 'helv' : 'Arial',
96 116 'other': 'Comic Sans MS',
97 117 'size' : 10,
98 118 'size2': 8,
99 119 }
100 120 elif wx.Platform == '__WXMAC__':
101 121 FACES = { 'times': 'Times New Roman',
102 122 'mono' : 'Monaco',
103 123 'helv' : 'Arial',
104 124 'other': 'Comic Sans MS',
105 125 'size' : 10,
106 126 'size2': 8,
107 127 }
108 128 else:
109 129 FACES = { 'times': 'Times',
110 130 'mono' : 'Courier',
111 131 'helv' : 'Helvetica',
112 132 'other': 'new century schoolbook',
113 133 'size' : 10,
114 134 'size2': 8,
115 135 }
116 136
117 137
118 138 #-------------------------------------------------------------------------------
119 139 # The console widget class
120 140 #-------------------------------------------------------------------------------
121 141 class ConsoleWidget(editwindow.EditWindow):
122 142 """ Specialized styled text control view for console-like workflow.
123 143
124 144 This widget is mainly interested in dealing with the prompt and
125 145 keeping the cursor inside the editing line.
126 146 """
127 147
128 148 # This is where the title captured from the ANSI escape sequences are
129 149 # stored.
130 150 title = 'Console'
131 151
132 152 # Last prompt printed
133 153 last_prompt = ''
134 154
135 155 # The buffer being edited.
136 156 def _set_input_buffer(self, string):
137 157 self.SetSelection(self.current_prompt_pos, self.GetLength())
138 158 self.ReplaceSelection(string)
139 159 self.GotoPos(self.GetLength())
140 160
141 161 def _get_input_buffer(self):
142 162 """ Returns the text in current edit buffer.
143 163 """
144 164 input_buffer = self.GetTextRange(self.current_prompt_pos,
145 165 self.GetLength())
146 166 input_buffer = input_buffer.replace(LINESEP, '\n')
147 167 return input_buffer
148 168
149 169 input_buffer = property(_get_input_buffer, _set_input_buffer)
150 170
151 171 style = _DEFAULT_STYLE.copy()
152 172
153 173 # Translation table from ANSI escape sequences to color. Override
154 174 # this to specify your colors.
155 175 ANSI_STYLES = ANSI_STYLES.copy()
156 176
157 177 # Font faces
158 178 faces = FACES.copy()
159 179
160 180 # Store the last time a refresh was done
161 181 _last_refresh_time = 0
162 182
163 183 #--------------------------------------------------------------------------
164 184 # Public API
165 185 #--------------------------------------------------------------------------
166 186
167 187 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
168 188 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
169 189 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
170 190 self.configure_scintilla()
171 191 # Track if 'enter' key as ever been processed
172 192 # This variable will only be reallowed until key goes up
173 193 self.enter_catched = False
174 194 self.current_prompt_pos = 0
175 195
176 196 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
177 197 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
178 198
179 199
180 200 def write(self, text, refresh=True):
181 201 """ Write given text to buffer, while translating the ansi escape
182 202 sequences.
183 203 """
184 204 # XXX: do not put print statements to sys.stdout/sys.stderr in
185 205 # this method, the print statements will call this method, as
186 206 # you will end up with an infinit loop
187 207 title = self.title_pat.split(text)
188 208 if len(title)>1:
189 209 self.title = title[-2]
190 210
191 211 text = self.title_pat.sub('', text)
192 212 segments = self.color_pat.split(text)
193 213 segment = segments.pop(0)
194 214 self.GotoPos(self.GetLength())
195 215 self.StartStyling(self.GetLength(), 0xFF)
196 216 try:
197 217 self.AppendText(segment)
198 218 except UnicodeDecodeError:
199 219 # XXX: Do I really want to skip the exception?
200 220 pass
201 221
202 222 if segments:
203 223 for ansi_tag, text in zip(segments[::2], segments[1::2]):
204 224 self.StartStyling(self.GetLength(), 0xFF)
205 225 try:
206 226 self.AppendText(text)
207 227 except UnicodeDecodeError:
208 228 # XXX: Do I really want to skip the exception?
209 229 pass
210 230
211 231 if ansi_tag not in self.ANSI_STYLES:
212 232 style = 0
213 233 else:
214 234 style = self.ANSI_STYLES[ansi_tag][0]
215 235
216 236 self.SetStyling(len(text), style)
217 237
218 238 self.GotoPos(self.GetLength())
219 239 if refresh:
220 240 current_time = time.time()
221 241 if current_time - self._last_refresh_time > 0.03:
222 242 if sys.platform == 'win32':
223 243 wx.SafeYield()
224 244 else:
225 245 wx.Yield()
226 246 # self.ProcessEvent(wx.PaintEvent())
227 247 self._last_refresh_time = current_time
228 248
229 249
230 250 def new_prompt(self, prompt):
231 251 """ Prints a prompt at start of line, and move the start of the
232 252 current block there.
233 253
234 254 The prompt can be given with ascii escape sequences.
235 255 """
236 256 self.write(prompt, refresh=False)
237 257 # now we update our cursor giving end of prompt
238 258 self.current_prompt_pos = self.GetLength()
239 259 self.current_prompt_line = self.GetCurrentLine()
240 260 self.EnsureCaretVisible()
241 261 self.last_prompt = prompt
242 262
243 263
244 264 def continuation_prompt(self):
245 265 """ Returns the current continuation prompt.
246 266 We need to implement this method here to deal with the
247 267 ascii escape sequences cleaning up.
248 268 """
249 269 # ASCII-less prompt
250 270 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
251 271 return "."*(len(ascii_less)-2) + ': '
252 272
253 273
254 274 def scroll_to_bottom(self):
255 275 maxrange = self.GetScrollRange(wx.VERTICAL)
256 276 self.ScrollLines(maxrange)
257 277
258 278
259 279 def pop_completion(self, possibilities, offset=0):
260 280 """ Pops up an autocompletion menu. Offset is the offset
261 281 in characters of the position at which the menu should
262 282 appear, relativ to the cursor.
263 283 """
264 284 self.AutoCompSetIgnoreCase(False)
265 285 self.AutoCompSetAutoHide(False)
266 286 self.AutoCompSetMaxHeight(len(possibilities))
267 287 self.AutoCompShow(offset, " ".join(possibilities))
268 288
269 289
270 290 def get_line_width(self):
271 291 """ Return the width of the line in characters.
272 292 """
273 293 return self.GetSize()[0]/self.GetCharWidth()
274 294
275 295
276 296 def configure_scintilla(self):
277
278 p = self.style
297 """ Set up all the styling option of the embedded scintilla
298 widget.
299 """
300 p = self.style.copy()
279 301
280 #First we define the special background colors
281 if 'trace' in p:
282 _COMPLETE_BUFFER_BG = p['trace']
283 else:
284 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
285
286 if 'stdout' in p:
287 _INPUT_BUFFER_BG = p['stdout']
288 else:
289 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
290
291 if 'stderr' in p:
292 _ERROR_BG = p['stderr']
293 else:
294 _ERROR_BG = '#FFF1F1' # Nice red
295
296 302 # Marker for complete buffer.
297 303 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
298 background = _COMPLETE_BUFFER_BG)
304 background=p['trace'])
299 305
300 306 # Marker for current input buffer.
301 307 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
302 background = _INPUT_BUFFER_BG)
308 background=p['stdout'])
303 309 # Marker for tracebacks.
304 310 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
305 background = _ERROR_BG)
311 background=p['stderr'])
306 312
307 313 self.SetEOLMode(stc.STC_EOL_LF)
308 314
309 315 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
310 316 # the widget
311 317 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
312 318 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
313 319 # Also allow Ctrl Shift "=" for poor non US keyboard users.
314 320 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
315 321 stc.STC_CMD_ZOOMIN)
316 322
317 323 # Keys: we need to clear some of the keys the that don't play
318 324 # well with a console.
319 325 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
320 326 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
321 327 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
322 328 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
323 329
324 330 self.SetEOLMode(stc.STC_EOL_CRLF)
325 331 self.SetWrapMode(stc.STC_WRAP_CHAR)
326 332 self.SetWrapMode(stc.STC_WRAP_WORD)
327 333 self.SetBufferedDraw(True)
328 334
329 if 'antialiasing' in p:
330 self.SetUseAntiAliasing(p['antialiasing'])
331 else:
332 self.SetUseAntiAliasing(True)
335 self.SetUseAntiAliasing(p['antialiasing'])
333 336
334 337 self.SetLayoutCache(stc.STC_CACHE_PAGE)
335 338 self.SetUndoCollection(False)
336 339 self.SetUseTabs(True)
337 340 self.SetIndent(4)
338 341 self.SetTabWidth(4)
339 342
340 343 # we don't want scintilla's autocompletion to choose
341 344 # automaticaly out of a single choice list, as we pop it up
342 345 # automaticaly
343 346 self.AutoCompSetChooseSingle(False)
344 347 self.AutoCompSetMaxHeight(10)
345 348 # XXX: this doesn't seem to have an effect.
346 349 self.AutoCompSetFillUps('\n')
347 350
348 351 self.SetMargins(3, 3) #text is moved away from border with 3px
349 352 # Suppressing Scintilla margins
350 353 self.SetMarginWidth(0, 0)
351 354 self.SetMarginWidth(1, 0)
352 355 self.SetMarginWidth(2, 0)
353 356
354 357 # Xterm escape sequences
355 358 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
356 359 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
357 360
358 361 # styles
359 362
360 if 'carret_color' in p:
361 self.SetCaretForeground(p['carret_color'])
362 else:
363 self.SetCaretForeground('BLACK')
363 self.SetCaretForeground(p['carret_color'])
364 364
365 if 'background_color' in p:
366 background_color = p['background_color']
367 else:
368 background_color = 'WHITE'
365 background_color = p['background_color']
369 366
370 367 if 'default' in p:
371 368 if 'back' not in p['default']:
372 369 p['default'] += ',back:%s' % background_color
373 370 if 'size' not in p['default']:
374 371 p['default'] += ',size:%s' % self.faces['size']
375 372 if 'face' not in p['default']:
376 373 p['default'] += ',face:%s' % self.faces['mono']
377 374
378 375 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
379 376 else:
380 377 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
381 378 "fore:%s,back:%s,size:%d,face:%s"
382 379 % (self.ANSI_STYLES['0;30'][1],
383 380 background_color,
384 381 self.faces['size'], self.faces['mono']))
385 382
386 #all styles = default one
387 383 self.StyleClearAll()
388 384
389 385 # XXX: two lines below are usefull if not using the lexer
390 386 #for style in self.ANSI_STYLES.values():
391 387 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
392 388
393 #prompt definition
394 if 'prompt_in1' in p:
395 self.prompt_in1 = p['prompt_in1']
396 else:
397 self.prompt_in1 = \
398 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
399
400 if 'prompt_out' in p:
401 self.prompt_out = p['prompt_out']
402 else:
403 self.prompt_out = \
404 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
389 # prompt definition
390 self.prompt_in1 = p['prompt_in1']
391 self.prompt_out = p['prompt_out']
405 392
406 393 self.output_prompt_template = string.Template(self.prompt_out)
407 394 self.input_prompt_template = string.Template(self.prompt_in1)
408 395
409 if 'stdout' in p:
410 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
411 if 'stderr' in p:
412 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
413 if 'trace' in p:
414 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
415 if 'bracegood' in p:
416 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
417 if 'bracebad' in p:
418 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
419 if 'comment' in p:
420 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
421 if 'number' in p:
422 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
423 if 'string' in p:
424 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
425 if 'char' in p:
426 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
427 if 'keyword' in p:
428 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
429 if 'keyword' in p:
430 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
431 if 'triple' in p:
432 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
433 if 'tripledouble' in p:
434 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
435 if 'class' in p:
436 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
437 if 'def' in p:
438 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
439 if 'operator' in p:
440 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
441 if 'comment' in p:
442 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
443
444 if 'edge_column' in p:
445 edge_column = p['edge_column']
446 if edge_column is not None and edge_column > 0:
447 #we add a vertical line to console widget
448 self.SetEdgeMode(stc.STC_EDGE_LINE)
449 self.SetEdgeColumn(88)
396 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
397 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
398 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
399 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
400 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
401 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
402 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
403 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
404 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
405 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
406 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
407 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
408 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
409 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
410 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
411 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
412 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
413
414 edge_column = p['edge_column']
415 if edge_column is not None and edge_column > 0:
416 #we add a vertical line to console widget
417 self.SetEdgeMode(stc.STC_EDGE_LINE)
418 self.SetEdgeColumn(edge_column)
450 419
451 420
452 421 #--------------------------------------------------------------------------
453 422 # EditWindow API
454 423 #--------------------------------------------------------------------------
455 424
456 425 def OnUpdateUI(self, event):
457 426 """ Override the OnUpdateUI of the EditWindow class, to prevent
458 427 syntax highlighting both for faster redraw, and for more
459 428 consistent look and feel.
460 429 """
461 430
462 431
463 432 #--------------------------------------------------------------------------
464 433 # Private API
465 434 #--------------------------------------------------------------------------
466 435
467 436 def _on_key_down(self, event, skip=True):
468 437 """ Key press callback used for correcting behavior for
469 438 console-like interfaces: the cursor is constraint to be after
470 439 the last prompt.
471 440
472 441 Return True if event as been catched.
473 442 """
474 443 catched = True
475 444 # XXX: Would the right way to do this be to have a
476 445 # dictionary at the instance level associating keys with
477 446 # callbacks? How would we deal with inheritance? And Do the
478 447 # different callbacks share local variables?
479 448
480 449 # Intercept some specific keys.
481 450 if event.KeyCode == ord('L') and event.ControlDown() :
482 451 self.scroll_to_bottom()
483 452 elif event.KeyCode == ord('K') and event.ControlDown() :
484 453 self.input_buffer = ''
485 454 elif event.KeyCode == ord('A') and event.ControlDown() :
486 455 self.GotoPos(self.GetLength())
487 456 self.SetSelectionStart(self.current_prompt_pos)
488 457 self.SetSelectionEnd(self.GetCurrentPos())
489 458 catched = True
490 459 elif event.KeyCode == ord('E') and event.ControlDown() :
491 460 self.GotoPos(self.GetLength())
492 461 catched = True
493 462 elif event.KeyCode == wx.WXK_PAGEUP:
494 463 self.ScrollPages(-1)
495 464 elif event.KeyCode == wx.WXK_PAGEDOWN:
496 465 self.ScrollPages(1)
497 466 elif event.KeyCode == wx.WXK_HOME:
498 467 self.GotoPos(self.GetLength())
499 468 elif event.KeyCode == wx.WXK_END:
500 469 self.GotoPos(self.GetLength())
501 470 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
502 471 self.ScrollLines(-1)
503 472 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
504 473 self.ScrollLines(1)
505 474 else:
506 475 catched = False
507 476
508 477 if self.AutoCompActive():
509 478 event.Skip()
510 479 else:
511 480 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
512 481 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
513 482 wx.MOD_SHIFT):
514 483 catched = True
515 484 if not self.enter_catched:
516 485 self.CallTipCancel()
517 486 if event.Modifiers == wx.MOD_SHIFT:
518 487 # Try to force execution
519 488 self.GotoPos(self.GetLength())
520 489 self.write('\n' + self.continuation_prompt(),
521 490 refresh=False)
522 491 self._on_enter()
523 492 else:
524 493 self._on_enter()
525 494 self.enter_catched = True
526 495
527 496 elif event.KeyCode == wx.WXK_HOME:
528 497 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
529 498 self.GotoPos(self.current_prompt_pos)
530 499 catched = True
531 500
532 501 elif event.Modifiers == wx.MOD_SHIFT:
533 502 # FIXME: This behavior is not ideal: if the selection
534 503 # is already started, it will jump.
535 504 self.SetSelectionStart(self.current_prompt_pos)
536 505 self.SetSelectionEnd(self.GetCurrentPos())
537 506 catched = True
538 507
539 508 elif event.KeyCode == wx.WXK_UP:
540 509 if self.GetCurrentLine() > self.current_prompt_line:
541 510 if self.GetCurrentLine() == self.current_prompt_line + 1 \
542 511 and self.GetColumn(self.GetCurrentPos()) < \
543 512 self.GetColumn(self.current_prompt_pos):
544 513 self.GotoPos(self.current_prompt_pos)
545 514 else:
546 515 event.Skip()
547 516 catched = True
548 517
549 518 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
550 519 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
551 520 event.Skip()
552 521 catched = True
553 522
554 523 elif event.KeyCode == wx.WXK_RIGHT:
555 524 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
556 525 event.Skip()
557 526 catched = True
558 527
559 528
560 529 elif event.KeyCode == wx.WXK_DELETE:
561 530 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
562 531 event.Skip()
563 532 catched = True
564 533
565 534 if skip and not catched:
566 535 # Put the cursor back in the edit region
567 536 if not self._keep_cursor_in_buffer():
568 537 if not (self.GetCurrentPos() == self.GetLength()
569 538 and event.KeyCode == wx.WXK_DELETE):
570 539 event.Skip()
571 540 catched = True
572 541
573 542 return catched
574 543
575 544
576 545 def _on_key_up(self, event, skip=True):
577 546 """ If cursor is outside the editing region, put it back.
578 547 """
579 548 if skip:
580 549 event.Skip()
581 550 self._keep_cursor_in_buffer()
582 551
583 552
584 553 # XXX: I need to avoid the problem of having an empty glass;
585 554 def _keep_cursor_in_buffer(self, pos=None):
586 555 """ Checks if the cursor is where it is allowed to be. If not,
587 556 put it back.
588 557
589 558 Returns
590 559 -------
591 560 cursor_moved: Boolean
592 561 whether or not the cursor was moved by this routine.
593 562
594 563 Notes
595 564 ------
596 565 WARNING: This does proper checks only for horizontal
597 566 movements.
598 567 """
599 568 if pos is None:
600 569 current_pos = self.GetCurrentPos()
601 570 else:
602 571 current_pos = pos
603 572 if current_pos < self.current_prompt_pos:
604 573 self.GotoPos(self.current_prompt_pos)
605 574 return True
606 575 line_num = self.LineFromPosition(current_pos)
607 576 if not current_pos > self.GetLength():
608 577 line_pos = self.GetColumn(current_pos)
609 578 else:
610 579 line_pos = self.GetColumn(self.GetLength())
611 580 line = self.GetLine(line_num)
612 581 # Jump the continuation prompt
613 582 continuation_prompt = self.continuation_prompt()
614 583 if ( line.startswith(continuation_prompt)
615 584 and line_pos < len(continuation_prompt)):
616 585 if line_pos < 2:
617 586 # We are at the beginning of the line, trying to move
618 587 # forward: jump forward.
619 588 self.GotoPos(current_pos + 1 +
620 589 len(continuation_prompt) - line_pos)
621 590 else:
622 591 # Jump back up
623 592 self.GotoPos(self.GetLineEndPosition(line_num-1))
624 593 return True
625 594 elif ( current_pos > self.GetLineEndPosition(line_num)
626 595 and not current_pos == self.GetLength()):
627 596 # Jump to next line
628 597 self.GotoPos(current_pos + 1 +
629 598 len(continuation_prompt))
630 599 return True
631 600
632 601 # We re-allow enter event processing
633 602 self.enter_catched = False
634 603 return False
635 604
636 605
637 606 if __name__ == '__main__':
638 607 # Some simple code to test the console widget.
639 608 class MainWindow(wx.Frame):
640 609 def __init__(self, parent, id, title):
641 610 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
642 611 self._sizer = wx.BoxSizer(wx.VERTICAL)
643 612 self.console_widget = ConsoleWidget(self)
644 613 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
645 614 self.SetSizer(self._sizer)
646 615 self.SetAutoLayout(1)
647 616 self.Show(True)
648 617
649 618 app = wx.PySimpleApp()
650 619 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
651 620 w.SetSize((780, 460))
652 621 w.Show()
653 622
654 623 app.MainLoop()
655 624
656 625
@@ -1,598 +1,601
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 This class inherits from ConsoleWidget, that provides a console-like
9 9 widget to provide a text-rendering widget suitable for a terminal.
10 10 """
11 11
12 12 __docformat__ = "restructuredtext en"
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-------------------------------------------------------------------------------
20 20
21 21 #-------------------------------------------------------------------------------
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24
25 25 # Major library imports
26 26 import re
27 27 import __builtin__
28 28 import sys
29 29 from threading import Lock
30 30
31 31 import wx
32 32 from wx import stc
33 33
34 34 # Ipython-specific imports.
35 from IPython.frontend._process import PipedProcess
35 from IPython.frontend.process import PipedProcess
36 36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 37 _ERROR_MARKER, _INPUT_MARKER
38 38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39 39
40 40 #-------------------------------------------------------------------------------
41 41 # Classes to implement the Wx frontend
42 42 #-------------------------------------------------------------------------------
43 43 class WxController(ConsoleWidget, PrefilterFrontEnd):
44 44 """Classes to provide a Wx frontend to the
45 45 IPython.kernel.core.interpreter.
46 46
47 47 This class inherits from ConsoleWidget, that provides a console-like
48 48 widget to provide a text-rendering widget suitable for a terminal.
49 49 """
50 50
51 51 # Print debug info on what is happening to the console.
52 52 debug = False
53 53
54 54 # The title of the terminal, as captured through the ANSI escape
55 55 # sequences.
56 56 def _set_title(self, title):
57 57 return self.Parent.SetTitle(title)
58 58
59 59 def _get_title(self):
60 60 return self.Parent.GetTitle()
61 61
62 62 title = property(_get_title, _set_title)
63 63
64 64
65 65 # The buffer being edited.
66 66 # We are duplicating the definition here because of multiple
67 67 # inheritence
68 68 def _set_input_buffer(self, string):
69 69 ConsoleWidget._set_input_buffer(self, string)
70 70 self._colorize_input_buffer()
71 71
72 72 def _get_input_buffer(self):
73 73 """ Returns the text in current edit buffer.
74 74 """
75 75 return ConsoleWidget._get_input_buffer(self)
76 76
77 77 input_buffer = property(_get_input_buffer, _set_input_buffer)
78 78
79 79
80 80 #--------------------------------------------------------------------------
81 81 # Private Attributes
82 82 #--------------------------------------------------------------------------
83 83
84 84 # A flag governing the behavior of the input. Can be:
85 85 #
86 86 # 'readline' for readline-like behavior with a prompt
87 87 # and an edit buffer.
88 88 # 'raw_input' similar to readline, but triggered by a raw-input
89 89 # call. Can be used by subclasses to act differently.
90 90 # 'subprocess' for sending the raw input directly to a
91 91 # subprocess.
92 92 # 'buffering' for buffering of the input, that will be used
93 93 # when the input state switches back to another state.
94 94 _input_state = 'readline'
95 95
96 96 # Attribute to store reference to the pipes of a subprocess, if we
97 97 # are running any.
98 98 _running_process = False
99 99
100 100 # A queue for writing fast streams to the screen without flooding the
101 101 # event loop
102 102 _out_buffer = []
103 103
104 104 # A lock to lock the _out_buffer to make sure we don't empty it
105 105 # while it is being swapped
106 106 _out_buffer_lock = Lock()
107 107
108 108 # The different line markers used to higlight the prompts.
109 109 _markers = dict()
110 110
111 111 #--------------------------------------------------------------------------
112 112 # Public API
113 113 #--------------------------------------------------------------------------
114 114
115 115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
116 116 size=wx.DefaultSize,
117 117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 118 styledef=None,
119 119 *args, **kwds):
120 120 """ Create Shell instance.
121 121
122 122 Parameters
123 123 -----------
124 124 styledef : dict, optional
125 125 styledef is the dictionary of options used to define the
126 126 style.
127 127 """
128 128 if styledef is not None:
129 129 self.style = styledef
130 130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
131 131 PrefilterFrontEnd.__init__(self, **kwds)
132 132
133 133 # Stick in our own raw_input:
134 134 self.ipython0.raw_input = self.raw_input
135 135
136 136 # A time for flushing the write buffer
137 137 BUFFER_FLUSH_TIMER_ID = 100
138 138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
139 139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
140 140
141 141 if 'debug' in kwds:
142 142 self.debug = kwds['debug']
143 143 kwds.pop('debug')
144 144
145 145 # Inject self in namespace, for debug
146 146 if self.debug:
147 147 self.shell.user_ns['self'] = self
148 148 # Inject our own raw_input in namespace
149 149 self.shell.user_ns['raw_input'] = self.raw_input
150 150
151 151 def raw_input(self, prompt=''):
152 152 """ A replacement from python's raw_input.
153 153 """
154 154 self.new_prompt(prompt)
155 155 self._input_state = 'raw_input'
156 156 if hasattr(self, '_cursor'):
157 157 del self._cursor
158 158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
159 159 self.__old_on_enter = self._on_enter
160 160 event_loop = wx.EventLoop()
161 161 def my_on_enter():
162 162 event_loop.Exit()
163 163 self._on_enter = my_on_enter
164 164 # XXX: Running a separate event_loop. Ugly.
165 165 event_loop.Run()
166 166 self._on_enter = self.__old_on_enter
167 167 self._input_state = 'buffering'
168 168 self._cursor = wx.BusyCursor()
169 169 return self.input_buffer.rstrip('\n')
170 170
171 171
172 172 def system_call(self, command_string):
173 173 self._input_state = 'subprocess'
174 174 event_loop = wx.EventLoop()
175 175 def _end_system_call():
176 176 self._input_state = 'buffering'
177 177 self._running_process = False
178 178 event_loop.Exit()
179 179
180 180 self._running_process = PipedProcess(command_string,
181 181 out_callback=self.buffered_write,
182 182 end_callback = _end_system_call)
183 183 self._running_process.start()
184 184 # XXX: Running a separate event_loop. Ugly.
185 185 event_loop.Run()
186 186 # Be sure to flush the buffer.
187 187 self._buffer_flush(event=None)
188 188
189 189
190 190 def do_calltip(self):
191 191 """ Analyse current and displays useful calltip for it.
192 192 """
193 193 if self.debug:
194 194 print >>sys.__stdout__, "do_calltip"
195 195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
196 196 symbol = self.input_buffer
197 197 symbol_string = separators.split(symbol)[-1]
198 198 base_symbol_string = symbol_string.split('.')[0]
199 199 if base_symbol_string in self.shell.user_ns:
200 200 symbol = self.shell.user_ns[base_symbol_string]
201 201 elif base_symbol_string in self.shell.user_global_ns:
202 202 symbol = self.shell.user_global_ns[base_symbol_string]
203 203 elif base_symbol_string in __builtin__.__dict__:
204 204 symbol = __builtin__.__dict__[base_symbol_string]
205 205 else:
206 206 return False
207 207 try:
208 208 for name in symbol_string.split('.')[1:] + ['__doc__']:
209 209 symbol = getattr(symbol, name)
210 210 self.AutoCompCancel()
211 211 # Check that the symbol can indeed be converted to a string:
212 212 symbol += ''
213 213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
214 214 except:
215 215 # The retrieve symbol couldn't be converted to a string
216 216 pass
217 217
218 218
219 219 def _popup_completion(self, create=False):
220 220 """ Updates the popup completion menu if it exists. If create is
221 221 true, open the menu.
222 222 """
223 223 if self.debug:
224 224 print >>sys.__stdout__, "_popup_completion"
225 225 line = self.input_buffer
226 226 if (self.AutoCompActive() and line and not line[-1] == '.') \
227 227 or create==True:
228 228 suggestion, completions = self.complete(line)
229 229 if completions:
230 230 offset = len(self._get_completion_text(line))
231 231 self.pop_completion(completions, offset=offset)
232 232 if self.debug:
233 233 print >>sys.__stdout__, completions
234 234
235 235
236 236 def buffered_write(self, text):
237 237 """ A write method for streams, that caches the stream in order
238 238 to avoid flooding the event loop.
239 239
240 240 This can be called outside of the main loop, in separate
241 241 threads.
242 242 """
243 243 self._out_buffer_lock.acquire()
244 244 self._out_buffer.append(text)
245 245 self._out_buffer_lock.release()
246 246 if not self._buffer_flush_timer.IsRunning():
247 247 wx.CallAfter(self._buffer_flush_timer.Start,
248 248 milliseconds=100, oneShot=True)
249 249
250 250
251 251 def clear_screen(self):
252 252 """ Empty completely the widget.
253 253 """
254 254 self.ClearAll()
255 255 self.new_prompt(self.input_prompt_template.substitute(
256 256 number=(self.last_result['number'] + 1)))
257 257
258 258
259 259 #--------------------------------------------------------------------------
260 260 # LineFrontEnd interface
261 261 #--------------------------------------------------------------------------
262 262
263 263 def execute(self, python_string, raw_string=None):
264 264 self._input_state = 'buffering'
265 265 self.CallTipCancel()
266 266 self._cursor = wx.BusyCursor()
267 267 if raw_string is None:
268 268 raw_string = python_string
269 269 end_line = self.current_prompt_line \
270 270 + max(1, len(raw_string.split('\n'))-1)
271 271 for i in range(self.current_prompt_line, end_line):
272 272 if i in self._markers:
273 273 self.MarkerDeleteHandle(self._markers[i])
274 274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
275 275 # Use a callafter to update the display robustly under windows
276 276 def callback():
277 277 self.GotoPos(self.GetLength())
278 278 PrefilterFrontEnd.execute(self, python_string,
279 279 raw_string=raw_string)
280 280 wx.CallAfter(callback)
281 281
282 282
283 283 def execute_command(self, command, hidden=False):
284 284 """ Execute a command, not only in the model, but also in the
285 285 view.
286 286 """
287 287 # XXX: This method needs to be integrated in the base fronted
288 288 # interface
289 289 if hidden:
290 290 return self.shell.execute(command)
291 291 else:
292 292 # XXX: we are not storing the input buffer previous to the
293 293 # execution, as this forces us to run the execution
294 294 # input_buffer a yield, which is not good.
295 295 ##current_buffer = self.shell.control.input_buffer
296 296 command = command.rstrip()
297 297 if len(command.split('\n')) > 1:
298 298 # The input command is several lines long, we need to
299 299 # force the execution to happen
300 300 command += '\n'
301 301 cleaned_command = self.prefilter_input(command)
302 302 self.input_buffer = command
303 303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
304 304 # recursive yields.
305 305 self.ProcessEvent(wx.PaintEvent())
306 306 self.write('\n')
307 307 if not self.is_complete(cleaned_command + '\n'):
308 308 self._colorize_input_buffer()
309 309 self.render_error('Incomplete or invalid input')
310 310 self.new_prompt(self.input_prompt_template.substitute(
311 311 number=(self.last_result['number'] + 1)))
312 312 return False
313 313 self._on_enter()
314 314 return True
315 315
316 316
317 317 def save_output_hooks(self):
318 318 self.__old_raw_input = __builtin__.raw_input
319 319 PrefilterFrontEnd.save_output_hooks(self)
320 320
321 321 def capture_output(self):
322 322 self.SetLexer(stc.STC_LEX_NULL)
323 323 PrefilterFrontEnd.capture_output(self)
324 324 __builtin__.raw_input = self.raw_input
325 325
326 326
327 327 def release_output(self):
328 328 __builtin__.raw_input = self.__old_raw_input
329 329 PrefilterFrontEnd.release_output(self)
330 330 self.SetLexer(stc.STC_LEX_PYTHON)
331 331
332 332
333 333 def after_execute(self):
334 334 PrefilterFrontEnd.after_execute(self)
335 335 # Clear the wait cursor
336 336 if hasattr(self, '_cursor'):
337 337 del self._cursor
338 338 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
339 339
340 340
341 341 def show_traceback(self):
342 342 start_line = self.GetCurrentLine()
343 343 PrefilterFrontEnd.show_traceback(self)
344 344 self.ProcessEvent(wx.PaintEvent())
345 345 #wx.Yield()
346 346 for i in range(start_line, self.GetCurrentLine()):
347 347 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
348 348
349 349
350 350 #--------------------------------------------------------------------------
351 351 # FrontEndBase interface
352 352 #--------------------------------------------------------------------------
353 353
354 354 def render_error(self, e):
355 355 start_line = self.GetCurrentLine()
356 356 self.write('\n' + e + '\n')
357 357 for i in range(start_line, self.GetCurrentLine()):
358 358 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
359 359
360 360
361 361 #--------------------------------------------------------------------------
362 362 # ConsoleWidget interface
363 363 #--------------------------------------------------------------------------
364 364
365 365 def new_prompt(self, prompt):
366 366 """ Display a new prompt, and start a new input buffer.
367 367 """
368 368 self._input_state = 'readline'
369 369 ConsoleWidget.new_prompt(self, prompt)
370 370 i = self.current_prompt_line
371 371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
372 372
373 373
374 374 def continuation_prompt(self, *args, **kwargs):
375 375 # Avoid multiple inheritence, be explicit about which
376 376 # parent method class gets called
377 377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
378 378
379 379
380 380 def write(self, *args, **kwargs):
381 381 # Avoid multiple inheritence, be explicit about which
382 382 # parent method class gets called
383 383 return ConsoleWidget.write(self, *args, **kwargs)
384 384
385 385
386 386 def _on_key_down(self, event, skip=True):
387 387 """ Capture the character events, let the parent
388 388 widget handle them, and put our logic afterward.
389 389 """
390 390 # FIXME: This method needs to be broken down in smaller ones.
391 391 current_line_num = self.GetCurrentLine()
392 392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
393 393 # Capture Control-C
394 394 if self._input_state == 'subprocess':
395 395 if self.debug:
396 396 print >>sys.__stderr__, 'Killing running process'
397 397 if hasattr(self._running_process, 'process'):
398 398 self._running_process.process.kill()
399 399 elif self._input_state == 'buffering':
400 400 if self.debug:
401 401 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
402 402 raise KeyboardInterrupt
403 403 # XXX: We need to make really sure we
404 404 # get back to a prompt.
405 405 elif self._input_state == 'subprocess' and (
406 406 ( event.KeyCode<256 and
407 407 not event.ControlDown() )
408 408 or
409 409 ( event.KeyCode in (ord('d'), ord('D')) and
410 410 event.ControlDown())):
411 411 # We are running a process, we redirect keys.
412 412 ConsoleWidget._on_key_down(self, event, skip=skip)
413 413 char = chr(event.KeyCode)
414 414 # Deal with some inconsistency in wx keycodes:
415 415 if char == '\r':
416 416 char = '\n'
417 417 elif not event.ShiftDown():
418 418 char = char.lower()
419 419 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
420 420 char = '\04'
421 421 self._running_process.process.stdin.write(char)
422 422 self._running_process.process.stdin.flush()
423 423 elif event.KeyCode in (ord('('), 57, 53):
424 424 # Calltips
425 425 event.Skip()
426 426 self.do_calltip()
427 427 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
428 428 event.Skip()
429 429 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
430 430 wx.CallAfter(self._popup_completion, create=True)
431 431 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
432 432 wx.WXK_RIGHT, wx.WXK_ESCAPE):
433 433 wx.CallAfter(self._popup_completion)
434 434 else:
435 435 # Up history
436 436 if event.KeyCode == wx.WXK_UP and (
437 437 ( current_line_num == self.current_prompt_line and
438 438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
439 439 or event.ControlDown() ):
440 440 new_buffer = self.get_history_previous(
441 441 self.input_buffer)
442 442 if new_buffer is not None:
443 443 self.input_buffer = new_buffer
444 444 if self.GetCurrentLine() > self.current_prompt_line:
445 445 # Go to first line, for seemless history up.
446 446 self.GotoPos(self.current_prompt_pos)
447 447 # Down history
448 448 elif event.KeyCode == wx.WXK_DOWN and (
449 449 ( current_line_num == self.LineCount -1 and
450 450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
451 451 or event.ControlDown() ):
452 452 new_buffer = self.get_history_next()
453 453 if new_buffer is not None:
454 454 self.input_buffer = new_buffer
455 455 # Tab-completion
456 456 elif event.KeyCode == ord('\t'):
457 457 current_line, current_line_num = self.CurLine
458 458 if not re.match(r'^\s*$', current_line):
459 459 self.complete_current_input()
460 460 if self.AutoCompActive():
461 461 wx.CallAfter(self._popup_completion, create=True)
462 462 else:
463 463 event.Skip()
464 464 elif event.KeyCode == wx.WXK_BACK:
465 465 # If characters where erased, check if we have to
466 466 # remove a line.
467 467 # XXX: What about DEL?
468 468 # FIXME: This logics should be in ConsoleWidget, as it is
469 469 # independant of IPython
470 470 current_line, _ = self.CurLine
471 471 current_pos = self.GetCurrentPos()
472 472 current_line_num = self.LineFromPosition(current_pos)
473 473 current_col = self.GetColumn(current_pos)
474 474 len_prompt = len(self.continuation_prompt())
475 475 if ( current_line.startswith(self.continuation_prompt())
476 476 and current_col == len_prompt):
477 477 new_lines = []
478 478 for line_num, line in enumerate(
479 479 self.input_buffer.split('\n')):
480 480 if (line_num + self.current_prompt_line ==
481 481 current_line_num):
482 482 new_lines.append(line[len_prompt:])
483 483 else:
484 484 new_lines.append('\n'+line)
485 485 # The first character is '\n', due to the above
486 486 # code:
487 487 self.input_buffer = ''.join(new_lines)[1:]
488 488 self.GotoPos(current_pos - 1 - len_prompt)
489 489 else:
490 490 ConsoleWidget._on_key_down(self, event, skip=skip)
491 491 else:
492 492 ConsoleWidget._on_key_down(self, event, skip=skip)
493 493
494 494
495 495
496 496 def _on_key_up(self, event, skip=True):
497 497 """ Called when any key is released.
498 498 """
499 499 if event.KeyCode in (59, ord('.')):
500 500 # Intercepting '.'
501 501 event.Skip()
502 502 wx.CallAfter(self._popup_completion, create=True)
503 503 else:
504 504 ConsoleWidget._on_key_up(self, event, skip=skip)
505 505 # Make sure the continuation_prompts are always followed by a
506 506 # whitespace
507 507 new_lines = []
508 508 if self._input_state == 'readline':
509 509 position = self.GetCurrentPos()
510 continuation_prompt = self.continuation_prompt()[:-1]
510 511 for line in self.input_buffer.split('\n'):
511 if not line == self.continuation_prompt()[:-1]:
512 if not line == continuation_prompt:
512 513 new_lines.append(line)
513 514 self.input_buffer = '\n'.join(new_lines)
514 515 self.GotoPos(position)
515 516
516 517
517 518 def _on_enter(self):
518 519 """ Called on return key down, in readline input_state.
519 520 """
520 521 last_line_num = self.LineFromPosition(self.GetLength())
521 522 current_line_num = self.LineFromPosition(self.GetCurrentPos())
522 523 new_line_pos = (last_line_num - current_line_num)
523 524 if self.debug:
524 525 print >>sys.__stdout__, repr(self.input_buffer)
525 526 self.write('\n', refresh=False)
526 527 # Under windows scintilla seems to be doing funny
527 528 # stuff to the line returns here, but the getter for
528 529 # input_buffer filters this out.
529 530 if sys.platform == 'win32':
530 531 self.input_buffer = self.input_buffer
532 old_prompt_num = self.current_prompt_pos
531 533 has_executed = PrefilterFrontEnd._on_enter(self,
532 534 new_line_pos=new_line_pos)
533 if not has_executed:
535 if old_prompt_num == self.current_prompt_pos:
536 # No execution has happened
534 537 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
535 538 return has_executed
536 539
537 540
538 541 #--------------------------------------------------------------------------
539 542 # EditWindow API
540 543 #--------------------------------------------------------------------------
541 544
542 545 def OnUpdateUI(self, event):
543 546 """ Override the OnUpdateUI of the EditWindow class, to prevent
544 547 syntax highlighting both for faster redraw, and for more
545 548 consistent look and feel.
546 549 """
547 550 if not self._input_state == 'readline':
548 551 ConsoleWidget.OnUpdateUI(self, event)
549 552
550 553 #--------------------------------------------------------------------------
551 554 # Private API
552 555 #--------------------------------------------------------------------------
553 556
554 557 def _buffer_flush(self, event):
555 558 """ Called by the timer to flush the write buffer.
556 559
557 560 This is always called in the mainloop, by the wx timer.
558 561 """
559 562 self._out_buffer_lock.acquire()
560 563 _out_buffer = self._out_buffer
561 564 self._out_buffer = []
562 565 self._out_buffer_lock.release()
563 566 self.write(''.join(_out_buffer), refresh=False)
564 567
565 568
566 569 def _colorize_input_buffer(self):
567 570 """ Keep the input buffer lines at a bright color.
568 571 """
569 572 if not self._input_state in ('readline', 'raw_input'):
570 573 return
571 574 end_line = self.GetCurrentLine()
572 575 if not sys.platform == 'win32':
573 576 end_line += 1
574 577 for i in range(self.current_prompt_line, end_line):
575 578 if i in self._markers:
576 579 self.MarkerDeleteHandle(self._markers[i])
577 580 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
578 581
579 582
580 583 if __name__ == '__main__':
581 584 class MainWindow(wx.Frame):
582 585 def __init__(self, parent, id, title):
583 586 wx.Frame.__init__(self, parent, id, title, size=(300,250))
584 587 self._sizer = wx.BoxSizer(wx.VERTICAL)
585 588 self.shell = WxController(self)
586 589 self._sizer.Add(self.shell, 1, wx.EXPAND)
587 590 self.SetSizer(self._sizer)
588 591 self.SetAutoLayout(1)
589 592 self.Show(True)
590 593
591 594 app = wx.PySimpleApp()
592 595 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
593 596 frame.shell.SetFocus()
594 597 frame.SetSize((680, 460))
595 598 self = frame.shell
596 599
597 600 app.MainLoop()
598 601
@@ -1,279 +1,279
1 1 # encoding: utf-8
2 2
3 3 """
4 4 This module defines the things that are used in setup.py for building IPython
5 5
6 6 This includes:
7 7
8 8 * The basic arguments to setup
9 9 * Functions for finding things like packages, package data, etc.
10 10 * A function for checking dependencies.
11 11 """
12 12
13 13 __docformat__ = "restructuredtext en"
14 14
15 15 #-------------------------------------------------------------------------------
16 16 # Copyright (C) 2008 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-------------------------------------------------------------------------------
21 21
22 22 #-------------------------------------------------------------------------------
23 23 # Imports
24 24 #-------------------------------------------------------------------------------
25 25
26 26 import os, sys
27 27
28 28 from glob import glob
29 29
30 30 from setupext import install_data_ext
31 31
32 32 #-------------------------------------------------------------------------------
33 33 # Useful globals and utility functions
34 34 #-------------------------------------------------------------------------------
35 35
36 36 # A few handy globals
37 37 isfile = os.path.isfile
38 38 pjoin = os.path.join
39 39
40 40 def oscmd(s):
41 41 print ">", s
42 42 os.system(s)
43 43
44 44 # A little utility we'll need below, since glob() does NOT allow you to do
45 45 # exclusion on multiple endings!
46 46 def file_doesnt_endwith(test,endings):
47 47 """Return true if test is a file and its name does NOT end with any
48 48 of the strings listed in endings."""
49 49 if not isfile(test):
50 50 return False
51 51 for e in endings:
52 52 if test.endswith(e):
53 53 return False
54 54 return True
55 55
56 56 #---------------------------------------------------------------------------
57 57 # Basic project information
58 58 #---------------------------------------------------------------------------
59 59
60 60 # Release.py contains version, authors, license, url, keywords, etc.
61 61 execfile(pjoin('IPython','Release.py'))
62 62
63 63 # Create a dict with the basic information
64 64 # This dict is eventually passed to setup after additional keys are added.
65 65 setup_args = dict(
66 66 name = name,
67 67 version = version,
68 68 description = description,
69 69 long_description = long_description,
70 70 author = author,
71 71 author_email = author_email,
72 72 url = url,
73 73 download_url = download_url,
74 74 license = license,
75 75 platforms = platforms,
76 76 keywords = keywords,
77 77 cmdclass = {'install_data': install_data_ext},
78 78 )
79 79
80 80
81 81 #---------------------------------------------------------------------------
82 82 # Find packages
83 83 #---------------------------------------------------------------------------
84 84
85 85 def add_package(packages,pname,config=False,tests=False,scripts=False,
86 86 others=None):
87 87 """
88 88 Add a package to the list of packages, including certain subpackages.
89 89 """
90 90 packages.append('.'.join(['IPython',pname]))
91 91 if config:
92 92 packages.append('.'.join(['IPython',pname,'config']))
93 93 if tests:
94 94 packages.append('.'.join(['IPython',pname,'tests']))
95 95 if scripts:
96 96 packages.append('.'.join(['IPython',pname,'scripts']))
97 97 if others is not None:
98 98 for o in others:
99 99 packages.append('.'.join(['IPython',pname,o]))
100 100
101 101 def find_packages():
102 102 """
103 103 Find all of IPython's packages.
104 104 """
105 105 packages = ['IPython']
106 106 add_package(packages, 'config', tests=True)
107 107 add_package(packages , 'Extensions')
108 108 add_package(packages, 'external')
109 109 add_package(packages, 'gui')
110 110 add_package(packages, 'gui.wx')
111 111 add_package(packages, 'frontend', tests=True)
112 add_package(packages, 'frontend._process')
112 add_package(packages, 'frontend.process')
113 113 add_package(packages, 'frontend.wx')
114 114 add_package(packages, 'frontend.cocoa', tests=True)
115 115 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
116 116 add_package(packages, 'kernel.core', config=True, tests=True)
117 117 add_package(packages, 'testing', tests=True)
118 118 add_package(packages, 'tests')
119 119 add_package(packages, 'testing.plugin', tests=False)
120 120 add_package(packages, 'tools', tests=True)
121 121 add_package(packages, 'UserConfig')
122 122 return packages
123 123
124 124 #---------------------------------------------------------------------------
125 125 # Find package data
126 126 #---------------------------------------------------------------------------
127 127
128 128 def find_package_data():
129 129 """
130 130 Find IPython's package_data.
131 131 """
132 132 # This is not enough for these things to appear in an sdist.
133 133 # We need to muck with the MANIFEST to get this to work
134 134 package_data = {
135 135 'IPython.UserConfig' : ['*'],
136 136 'IPython.tools.tests' : ['*.txt'],
137 137 'IPython.testing' : ['*.txt']
138 138 }
139 139 return package_data
140 140
141 141
142 142 #---------------------------------------------------------------------------
143 143 # Find data files
144 144 #---------------------------------------------------------------------------
145 145
146 146 def make_dir_struct(tag,base,out_base):
147 147 """Make the directory structure of all files below a starting dir.
148 148
149 149 This is just a convenience routine to help build a nested directory
150 150 hierarchy because distutils is too stupid to do this by itself.
151 151
152 152 XXX - this needs a proper docstring!
153 153 """
154 154
155 155 # we'll use these a lot below
156 156 lbase = len(base)
157 157 pathsep = os.path.sep
158 158 lpathsep = len(pathsep)
159 159
160 160 out = []
161 161 for (dirpath,dirnames,filenames) in os.walk(base):
162 162 # we need to strip out the dirpath from the base to map it to the
163 163 # output (installation) path. This requires possibly stripping the
164 164 # path separator, because otherwise pjoin will not work correctly
165 165 # (pjoin('foo/','/bar') returns '/bar').
166 166
167 167 dp_eff = dirpath[lbase:]
168 168 if dp_eff.startswith(pathsep):
169 169 dp_eff = dp_eff[lpathsep:]
170 170 # The output path must be anchored at the out_base marker
171 171 out_path = pjoin(out_base,dp_eff)
172 172 # Now we can generate the final filenames. Since os.walk only produces
173 173 # filenames, we must join back with the dirpath to get full valid file
174 174 # paths:
175 175 pfiles = [pjoin(dirpath,f) for f in filenames]
176 176 # Finally, generate the entry we need, which is a triple of (tag,output
177 177 # path, files) for use as a data_files parameter in install_data.
178 178 out.append((tag,out_path,pfiles))
179 179
180 180 return out
181 181
182 182
183 183 def find_data_files():
184 184 """
185 185 Find IPython's data_files.
186 186
187 187 Most of these are docs.
188 188 """
189 189
190 190 docdirbase = 'share/doc/ipython'
191 191 manpagebase = 'share/man/man1'
192 192
193 193 # Simple file lists can be made by hand
194 194 manpages = filter(isfile, glob('docs/man/*.1.gz'))
195 195 igridhelpfiles = filter(isfile, glob('IPython/Extensions/igrid_help.*'))
196 196
197 197 # For nested structures, use the utility above
198 198 example_files = make_dir_struct('data','docs/examples',
199 199 pjoin(docdirbase,'examples'))
200 200 manual_files = make_dir_struct('data','docs/dist',pjoin(docdirbase,'manual'))
201 201
202 202 # And assemble the entire output list
203 203 data_files = [ ('data',manpagebase, manpages),
204 204 ('data',pjoin(docdirbase,'extensions'),igridhelpfiles),
205 205 ] + manual_files + example_files
206 206
207 207 ## import pprint # dbg
208 208 ## print '*'*80
209 209 ## print 'data files'
210 210 ## pprint.pprint(data_files)
211 211 ## print '*'*80
212 212
213 213 return data_files
214 214
215 215 #---------------------------------------------------------------------------
216 216 # Find scripts
217 217 #---------------------------------------------------------------------------
218 218
219 219 def find_scripts():
220 220 """
221 221 Find IPython's scripts.
222 222 """
223 223 scripts = ['IPython/kernel/scripts/ipengine',
224 224 'IPython/kernel/scripts/ipcontroller',
225 225 'IPython/kernel/scripts/ipcluster',
226 226 'scripts/ipython',
227 227 'scripts/ipythonx',
228 228 'scripts/ipython-wx',
229 229 'scripts/pycolor',
230 230 'scripts/irunner',
231 231 'scripts/iptest',
232 232 ]
233 233
234 234 # Script to be run by the windows binary installer after the default setup
235 235 # routine, to add shortcuts and similar windows-only things. Windows
236 236 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
237 237 # doesn't find them.
238 238 if 'bdist_wininst' in sys.argv:
239 239 if len(sys.argv) > 2 and ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
240 240 print >> sys.stderr,"ERROR: bdist_wininst must be run alone. Exiting."
241 241 sys.exit(1)
242 242 scripts.append('scripts/ipython_win_post_install.py')
243 243
244 244 return scripts
245 245
246 246 #---------------------------------------------------------------------------
247 247 # Verify all dependencies
248 248 #---------------------------------------------------------------------------
249 249
250 250 def check_for_dependencies():
251 251 """Check for IPython's dependencies.
252 252
253 253 This function should NOT be called if running under setuptools!
254 254 """
255 255 from setupext.setupext import (
256 256 print_line, print_raw, print_status, print_message,
257 257 check_for_zopeinterface, check_for_twisted,
258 258 check_for_foolscap, check_for_pyopenssl,
259 259 check_for_sphinx, check_for_pygments,
260 260 check_for_nose, check_for_pexpect
261 261 )
262 262 print_line()
263 263 print_raw("BUILDING IPYTHON")
264 264 print_status('python', sys.version)
265 265 print_status('platform', sys.platform)
266 266 if sys.platform == 'win32':
267 267 print_status('Windows version', sys.getwindowsversion())
268 268
269 269 print_raw("")
270 270 print_raw("OPTIONAL DEPENDENCIES")
271 271
272 272 check_for_zopeinterface()
273 273 check_for_twisted()
274 274 check_for_foolscap()
275 275 check_for_pyopenssl()
276 276 check_for_sphinx()
277 277 check_for_pygments()
278 278 check_for_nose()
279 279 check_for_pexpect()
General Comments 0
You need to be logged in to leave comments. Login now