##// END OF EJS Templates
Merged Gael's ipython-sync-frontend branch with important mods....
Brian Granger -
r1949:a093b34b merge
parent child Browse files
Show More
@@ -0,0 +1,37 b''
1 # encoding: utf-8
2 """
3 Test the LineFrontEnd
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 from IPython.frontend.linefrontendbase import LineFrontEndBase
16 from copy import deepcopy
17 import nose.tools as nt
18
19 class ConcreteLineFrontEnd(LineFrontEndBase):
20 """ A concrete class to test the LineFrontEndBase.
21 """
22 def capture_output(self):
23 pass
24
25 def release_output(self):
26 pass
27
28
29 def test_is_complete():
30 """ Tests line completion heuristic.
31 """
32 frontend = ConcreteLineFrontEnd()
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')
36
37
@@ -18,10 +18,8 b' __docformat__ = "restructuredtext en"'
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 import re
19 import re
20
20
21 import IPython
22 import sys
21 import sys
23 import codeop
22 import codeop
24 import traceback
25
23
26 from frontendbase import FrontEndBase
24 from frontendbase import FrontEndBase
27 from IPython.kernel.core.interpreter import Interpreter
25 from IPython.kernel.core.interpreter import Interpreter
@@ -58,6 +56,9 b' class LineFrontEndBase(FrontEndBase):'
58 # programatic control of the frontend.
56 # programatic control of the frontend.
59 last_result = dict(number=0)
57 last_result = dict(number=0)
60
58
59 # The last prompt displayed. Useful for continuation prompts.
60 last_prompt = ''
61
61 # The input buffer being edited
62 # The input buffer being edited
62 input_buffer = ''
63 input_buffer = ''
63
64
@@ -151,8 +152,12 b' class LineFrontEndBase(FrontEndBase):'
151 self.capture_output()
152 self.capture_output()
152 try:
153 try:
153 # Add line returns here, to make sure that the statement is
154 # Add line returns here, to make sure that the statement is
154 # complete.
155 # complete (except if '\' was used).
155 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
156 # This should probably be done in a different place (like
157 # maybe 'prefilter_input' method? For now, this works.
158 clean_string = string.rstrip('\n')
159 if not clean_string.endswith('\\'): clean_string +='\n\n'
160 is_complete = codeop.compile_command(clean_string,
156 "<string>", "exec")
161 "<string>", "exec")
157 self.release_output()
162 self.release_output()
158 except Exception, e:
163 except Exception, e:
@@ -183,16 +188,6 b' class LineFrontEndBase(FrontEndBase):'
183 # Create a false result, in case there is an exception
188 # Create a false result, in case there is an exception
184 self.last_result = dict(number=self.prompt_number)
189 self.last_result = dict(number=self.prompt_number)
185
190
186 ## try:
187 ## self.history.input_cache[-1] = raw_string.rstrip()
188 ## result = self.shell.execute(python_string)
189 ## self.last_result = result
190 ## self.render_result(result)
191 ## except:
192 ## self.show_traceback()
193 ## finally:
194 ## self.after_execute()
195
196 try:
191 try:
197 try:
192 try:
198 self.history.input_cache[-1] = raw_string.rstrip()
193 self.history.input_cache[-1] = raw_string.rstrip()
@@ -272,15 +267,15 b' class LineFrontEndBase(FrontEndBase):'
272 symbols_per_line = max(1, chars_per_line/max_len)
267 symbols_per_line = max(1, chars_per_line/max_len)
273
268
274 pos = 1
269 pos = 1
275 buf = []
270 completion_string = []
276 for symbol in possibilities:
271 for symbol in possibilities:
277 if pos < symbols_per_line:
272 if pos < symbols_per_line:
278 buf.append(symbol.ljust(max_len))
273 completion_string.append(symbol.ljust(max_len))
279 pos += 1
274 pos += 1
280 else:
275 else:
281 buf.append(symbol.rstrip() + '\n')
276 completion_string.append(symbol.rstrip() + '\n')
282 pos = 1
277 pos = 1
283 self.write(''.join(buf))
278 self.write(''.join(completion_string))
284 self.new_prompt(self.input_prompt_template.substitute(
279 self.new_prompt(self.input_prompt_template.substitute(
285 number=self.last_result['number'] + 1))
280 number=self.last_result['number'] + 1))
286 self.input_buffer = new_line
281 self.input_buffer = new_line
@@ -297,26 +292,70 b' class LineFrontEndBase(FrontEndBase):'
297 self.write(prompt)
292 self.write(prompt)
298
293
299
294
295 def continuation_prompt(self):
296 """Returns the current continuation prompt.
297 """
298 return ("."*(len(self.last_prompt)-2) + ': ')
299
300
301 def execute_command(self, command, hidden=False):
302 """ Execute a command, not only in the model, but also in the
303 view, if any.
304 """
305 return self.shell.execute(command)
306
300 #--------------------------------------------------------------------------
307 #--------------------------------------------------------------------------
301 # Private API
308 # Private API
302 #--------------------------------------------------------------------------
309 #--------------------------------------------------------------------------
303
310
304 def _on_enter(self):
311 def _on_enter(self, new_line_pos=0):
305 """ Called when the return key is pressed in a line editing
312 """ Called when the return key is pressed in a line editing
306 buffer.
313 buffer.
314
315 Parameters
316 ----------
317 new_line_pos : integer, optional
318 Position of the new line to add, starting from the
319 end (0 adds a new line after the last line, -1 before
320 the last line...)
321
322 Returns
323 -------
324 True if execution is triggered
307 """
325 """
308 current_buffer = self.input_buffer
326 current_buffer = self.input_buffer
309 cleaned_buffer = self.prefilter_input(current_buffer)
327 # XXX: This string replace is ugly, but there should be no way it
328 # fails.
329 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
330 '', current_buffer).replace('\n' + self.continuation_prompt(),
331 '\n')
332 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
310 if self.is_complete(cleaned_buffer):
333 if self.is_complete(cleaned_buffer):
311 self.execute(cleaned_buffer, raw_string=current_buffer)
334 self.execute(cleaned_buffer, raw_string=current_buffer)
335 return True
312 else:
336 else:
313 self.input_buffer += self._get_indent_string(
337 # Start a new line.
314 current_buffer[:-1])
338 new_line_pos = -new_line_pos
315 if len(current_buffer.split('\n')) == 2:
339 lines = current_buffer.split('\n')[:-1]
316 self.input_buffer += '\t\t'
340 prompt_less_lines = prompt_less_buffer.split('\n')
317 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
341 # Create the new line, with the continuation prompt, and the
318 self.input_buffer += '\t'
342 # same amount of indent than the line above it.
319
343 new_line = self.continuation_prompt() + \
344 self._get_indent_string('\n'.join(
345 prompt_less_lines[:new_line_pos-1]))
346 if len(lines) == 1:
347 # We are starting a first continuation line. Indent it.
348 new_line += '\t'
349 elif current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
350 # The last line ends with ":", autoindent the new line.
351 new_line += '\t'
352
353 if new_line_pos == 0:
354 lines.append(new_line)
355 else:
356 lines.insert(new_line_pos, new_line)
357 self.input_buffer = '\n'.join(lines)
358
320
359
321 def _get_indent_string(self, string):
360 def _get_indent_string(self, string):
322 """ Return the string of whitespace that prefixes a line. Used to
361 """ Return the string of whitespace that prefixes a line. Used to
@@ -22,9 +22,10 b' __docformat__ = "restructuredtext en"'
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24 import sys
24 import sys
25
25 import pydoc
26 from linefrontendbase import LineFrontEndBase, common_prefix
26 import os
27 from frontendbase import FrontEndBase
27 import re
28 import __builtin__
28
29
29 from IPython.ipmaker import make_IPython
30 from IPython.ipmaker import make_IPython
30 from IPython.ipapi import IPApi
31 from IPython.ipapi import IPApi
@@ -33,9 +34,8 b' from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap'
33 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34
35
35 from IPython.genutils import Term
36 from IPython.genutils import Term
36 import pydoc
37
37 import os
38 from linefrontendbase import LineFrontEndBase, common_prefix
38 import sys
39
39
40
40
41 def mk_system_call(system_call_function, command):
41 def mk_system_call(system_call_function, command):
@@ -45,6 +45,8 b' def mk_system_call(system_call_function, command):'
45 """
45 """
46 def my_system_call(args):
46 def my_system_call(args):
47 system_call_function("%s %s" % (command, args))
47 system_call_function("%s %s" % (command, args))
48
49 my_system_call.__doc__ = "Calls %s" % command
48 return my_system_call
50 return my_system_call
49
51
50 #-------------------------------------------------------------------------------
52 #-------------------------------------------------------------------------------
@@ -69,6 +71,13 b' class PrefilterFrontEnd(LineFrontEndBase):'
69 ipython0: an optional ipython0 instance to use for command
71 ipython0: an optional ipython0 instance to use for command
70 prefiltering and completion.
72 prefiltering and completion.
71 """
73 """
74 # This is a hack to avoid the IPython exception hook to trigger
75 # on exceptions (https://bugs.launchpad.net/bugs/337105)
76 # XXX: This is horrible: module-leve monkey patching -> side
77 # effects.
78 from IPython import iplib
79 iplib.InteractiveShell.isthreaded = True
80
72 LineFrontEndBase.__init__(self, *args, **kwargs)
81 LineFrontEndBase.__init__(self, *args, **kwargs)
73 self.shell.output_trap = RedirectorOutputTrap(
82 self.shell.output_trap = RedirectorOutputTrap(
74 out_callback=self.write,
83 out_callback=self.write,
@@ -83,10 +92,16 b' class PrefilterFrontEnd(LineFrontEndBase):'
83 if ipython0 is None:
92 if ipython0 is None:
84 # Instanciate an IPython0 interpreter to be able to use the
93 # Instanciate an IPython0 interpreter to be able to use the
85 # prefiltering.
94 # prefiltering.
95 # Suppress all key input, to avoid waiting
96 def my_rawinput(x=None):
97 return '\n'
98 old_rawinput = __builtin__.raw_input
99 __builtin__.raw_input = my_rawinput
86 # XXX: argv=[] is a bit bold.
100 # XXX: argv=[] is a bit bold.
87 ipython0 = make_IPython(argv=[],
101 ipython0 = make_IPython(argv=[],
88 user_ns=self.shell.user_ns,
102 user_ns=self.shell.user_ns,
89 user_global_ns=self.shell.user_global_ns)
103 user_global_ns=self.shell.user_global_ns)
104 __builtin__.raw_input = old_rawinput
90 self.ipython0 = ipython0
105 self.ipython0 = ipython0
91 # Set the pager:
106 # Set the pager:
92 self.ipython0.set_hook('show_in_pager',
107 self.ipython0.set_hook('show_in_pager',
@@ -98,16 +113,17 b' class PrefilterFrontEnd(LineFrontEndBase):'
98 self._ip.system = self.system_call
113 self._ip.system = self.system_call
99 # XXX: Muck around with magics so that they work better
114 # XXX: Muck around with magics so that they work better
100 # in our environment
115 # in our environment
101 self.ipython0.magic_ls = mk_system_call(self.system_call,
116 if not sys.platform.startswith('win'):
102 'ls -CF')
117 self.ipython0.magic_ls = mk_system_call(self.system_call,
118 'ls -CF')
103 # And now clean up the mess created by ipython0
119 # And now clean up the mess created by ipython0
104 self.release_output()
120 self.release_output()
105
121
106
122
107 if not 'banner' in kwargs and self.banner is None:
123 if not 'banner' in kwargs and self.banner is None:
108 self.banner = self.ipython0.BANNER + """
124 self.banner = self.ipython0.BANNER
109 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
110
125
126 # FIXME: __init__ and start should be two different steps
111 self.start()
127 self.start()
112
128
113 #--------------------------------------------------------------------------
129 #--------------------------------------------------------------------------
@@ -117,7 +133,10 b' This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""'
117 def show_traceback(self):
133 def show_traceback(self):
118 """ Use ipython0 to capture the last traceback and display it.
134 """ Use ipython0 to capture the last traceback and display it.
119 """
135 """
120 self.capture_output()
136 # Don't do the capture; the except_hook has already done some
137 # modifications to the IO streams, if we store them, we'll be
138 # storing the wrong ones.
139 #self.capture_output()
121 self.ipython0.showtraceback(tb_offset=-1)
140 self.ipython0.showtraceback(tb_offset=-1)
122 self.release_output()
141 self.release_output()
123
142
@@ -171,7 +190,7 b' This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""'
171 def complete(self, line):
190 def complete(self, line):
172 # FIXME: This should be factored out in the linefrontendbase
191 # FIXME: This should be factored out in the linefrontendbase
173 # method.
192 # method.
174 word = line.split('\n')[-1].split(' ')[-1]
193 word = self._get_completion_text(line)
175 completions = self.ipython0.complete(word)
194 completions = self.ipython0.complete(word)
176 # FIXME: The proper sort should be done in the complete method.
195 # FIXME: The proper sort should be done in the complete method.
177 key = lambda x: x.replace('_', '')
196 key = lambda x: x.replace('_', '')
@@ -244,3 +263,18 b' This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""'
244 """
263 """
245 self.ipython0.atexit_operations()
264 self.ipython0.atexit_operations()
246
265
266
267 def _get_completion_text(self, line):
268 """ Returns the text to be completed by breaking the line at specified
269 delimiters.
270 """
271 # Break at: spaces, '=', all parentheses (except if balanced).
272 # FIXME2: In the future, we need to make the implementation similar to
273 # that in the 'pyreadline' module (modes/basemode.py) where we break at
274 # each delimiter and try to complete the residual line, until we get a
275 # successful list of completions.
276 expression = '\s|=|,|:|\((?!.*\))|\[(?!.*\])|\{(?!.*\})'
277 complete_sep = re.compile(expression)
278 text = complete_sep.split(line)[-1]
279 return text
280
1 NO CONTENT: file renamed from IPython/frontend/_process/__init__.py to IPython/frontend/process/__init__.py
NO CONTENT: file renamed from IPython/frontend/_process/__init__.py to IPython/frontend/process/__init__.py
@@ -151,7 +151,12 b' else:'
151 self._thread = ht
151 self._thread = ht
152 self.pid = pid
152 self.pid = pid
153
153
154 winprocess.AssignProcessToJobObject(self._job, hp)
154 # XXX: A try/except to fix UAC-related problems under
155 # Windows Vista, when reparenting jobs.
156 try:
157 winprocess.AssignProcessToJobObject(self._job, hp)
158 except WindowsError:
159 pass
155 winprocess.ResumeThread(ht)
160 winprocess.ResumeThread(ht)
156
161
157 if p2cread is not None:
162 if p2cread is not None:
1 NO CONTENT: file renamed from IPython/frontend/_process/pipedprocess.py to IPython/frontend/process/pipedprocess.py
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
NO CONTENT: file renamed from IPython/frontend/_process/winprocess.py to IPython/frontend/process/winprocess.py
@@ -15,9 +15,27 b' __docformat__ = "restructuredtext en"'
15 from cStringIO import StringIO
15 from cStringIO import StringIO
16 import string
16 import string
17
17
18 from nose.tools import assert_equal
19
18 from IPython.ipapi import get as get_ipython0
20 from IPython.ipapi import get as get_ipython0
19 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
21 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
20 from copy import deepcopy
22 from copy import copy, deepcopy
23
24 def safe_deepcopy(d):
25 """ Deep copy every key of the given dict, when possible. Elsewhere
26 do a copy.
27 """
28 copied_d = dict()
29 for key, value in d.iteritems():
30 try:
31 copied_d[key] = deepcopy(value)
32 except:
33 try:
34 copied_d[key] = copy(value)
35 except:
36 copied_d[key] = value
37 return copied_d
38
21
39
22 class TestPrefilterFrontEnd(PrefilterFrontEnd):
40 class TestPrefilterFrontEnd(PrefilterFrontEnd):
23
41
@@ -26,16 +44,8 b' class TestPrefilterFrontEnd(PrefilterFrontEnd):'
26 banner = ''
44 banner = ''
27
45
28 def __init__(self):
46 def __init__(self):
29 ipython0 = get_ipython0().IP
30 self.out = StringIO()
47 self.out = StringIO()
31 PrefilterFrontEnd.__init__(self, ipython0=ipython0)
48 PrefilterFrontEnd.__init__(self)
32 # Clean up the namespace for isolation between tests
33 user_ns = self.ipython0.user_ns
34 # We need to keep references to things so that they don't
35 # get garbage collected (this stinks).
36 self.shadow_ns = dict()
37 for i in self.ipython0.magic_who_ls():
38 self.shadow_ns[i] = user_ns.pop(i)
39 # Some more code for isolation (yeah, crazy)
49 # Some more code for isolation (yeah, crazy)
40 self._on_enter()
50 self._on_enter()
41 self.out.flush()
51 self.out.flush()
@@ -52,17 +62,31 b' class TestPrefilterFrontEnd(PrefilterFrontEnd):'
52
62
53 def isolate_ipython0(func):
63 def isolate_ipython0(func):
54 """ Decorator to isolate execution that involves an iptyhon0.
64 """ Decorator to isolate execution that involves an iptyhon0.
65
66 Notes
67 ------
68
69 Apply only to functions with no arguments. Nose skips functions
70 with arguments.
55 """
71 """
56 def my_func(*args, **kwargs):
72 def my_func():
57 ipython0 = get_ipython0().IP
73 iplib = get_ipython0()
58 user_ns = deepcopy(ipython0.user_ns)
74 if iplib is None:
59 global_ns = deepcopy(ipython0.global_ns)
75 return func()
76 ipython0 = iplib.IP
77 global_ns = safe_deepcopy(ipython0.user_global_ns)
78 user_ns = safe_deepcopy(ipython0.user_ns)
60 try:
79 try:
61 func(*args, **kwargs)
80 out = func()
62 finally:
81 finally:
63 ipython0.user_ns = user_ns
82 ipython0.user_ns = user_ns
64 ipython0.global_ns = global_ns
83 ipython0.user_global_ns = global_ns
84 # Undo the hack at creation of PrefilterFrontEnd
85 from IPython import iplib
86 iplib.InteractiveShell.isthreaded = False
87 return out
65
88
89 my_func.__name__ = func.__name__
66 return my_func
90 return my_func
67
91
68
92
@@ -74,7 +98,7 b' def test_execution():'
74 f.input_buffer = 'print 1'
98 f.input_buffer = 'print 1'
75 f._on_enter()
99 f._on_enter()
76 out_value = f.out.getvalue()
100 out_value = f.out.getvalue()
77 assert out_value == '1\n'
101 assert_equal(out_value, '1\n')
78
102
79
103
80 @isolate_ipython0
104 @isolate_ipython0
@@ -87,20 +111,20 b' def test_multiline():'
87 f.input_buffer += 'print 1'
111 f.input_buffer += 'print 1'
88 f._on_enter()
112 f._on_enter()
89 out_value = f.out.getvalue()
113 out_value = f.out.getvalue()
90 assert out_value == ''
114 yield assert_equal, out_value, ''
91 f._on_enter()
115 f._on_enter()
92 out_value = f.out.getvalue()
116 out_value = f.out.getvalue()
93 assert out_value == '1\n'
117 yield assert_equal, out_value, '1\n'
94 f = TestPrefilterFrontEnd()
118 f = TestPrefilterFrontEnd()
95 f.input_buffer='(1 +'
119 f.input_buffer='(1 +'
96 f._on_enter()
120 f._on_enter()
97 f.input_buffer += '0)'
121 f.input_buffer += '0)'
98 f._on_enter()
122 f._on_enter()
99 out_value = f.out.getvalue()
123 out_value = f.out.getvalue()
100 assert out_value == ''
124 yield assert_equal, out_value, ''
101 f._on_enter()
125 f._on_enter()
102 out_value = f.out.getvalue()
126 out_value = f.out.getvalue()
103 assert out_value == '1\n'
127 yield assert_equal, out_value, '1\n'
104
128
105
129
106 @isolate_ipython0
130 @isolate_ipython0
@@ -113,13 +137,13 b' def test_capture():'
113 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
137 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
114 f._on_enter()
138 f._on_enter()
115 out_value = f.out.getvalue()
139 out_value = f.out.getvalue()
116 assert out_value == '1'
140 yield assert_equal, out_value, '1'
117 f = TestPrefilterFrontEnd()
141 f = TestPrefilterFrontEnd()
118 f.input_buffer = \
142 f.input_buffer = \
119 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
143 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
120 f._on_enter()
144 f._on_enter()
121 out_value = f.out.getvalue()
145 out_value = f.out.getvalue()
122 assert out_value == '1'
146 yield assert_equal, out_value, '1'
123
147
124
148
125 @isolate_ipython0
149 @isolate_ipython0
@@ -132,7 +156,7 b' def test_magic():'
132 f.input_buffer += '%who'
156 f.input_buffer += '%who'
133 f._on_enter()
157 f._on_enter()
134 out_value = f.out.getvalue()
158 out_value = f.out.getvalue()
135 assert out_value == 'Interactive namespace is empty.\n'
159 assert_equal(out_value, 'Interactive namespace is empty.\n')
136
160
137
161
138 @isolate_ipython0
162 @isolate_ipython0
@@ -156,8 +180,8 b' def test_help():'
156
180
157
181
158 @isolate_ipython0
182 @isolate_ipython0
159 def test_completion():
183 def test_completion_simple():
160 """ Test command-line completion.
184 """ Test command-line completion on trivial examples.
161 """
185 """
162 f = TestPrefilterFrontEnd()
186 f = TestPrefilterFrontEnd()
163 f.input_buffer = 'zzza = 1'
187 f.input_buffer = 'zzza = 1'
@@ -167,8 +191,47 b' def test_completion():'
167 f.input_buffer = 'zz'
191 f.input_buffer = 'zz'
168 f.complete_current_input()
192 f.complete_current_input()
169 out_value = f.out.getvalue()
193 out_value = f.out.getvalue()
170 assert out_value == '\nzzza zzzb '
194 yield assert_equal, out_value, '\nzzza zzzb '
171 assert f.input_buffer == 'zzz'
195 yield assert_equal, f.input_buffer, 'zzz'
196
197
198 @isolate_ipython0
199 def test_completion_parenthesis():
200 """ Test command-line completion when a parenthesis is open.
201 """
202 f = TestPrefilterFrontEnd()
203 f.input_buffer = 'zzza = 1'
204 f._on_enter()
205 f.input_buffer = 'zzzb = 2'
206 f._on_enter()
207 f.input_buffer = 'map(zz'
208 f.complete_current_input()
209 out_value = f.out.getvalue()
210 yield assert_equal, out_value, '\nzzza zzzb '
211 yield assert_equal, f.input_buffer, 'map(zzz'
212
213
214 @isolate_ipython0
215 def test_completion_indexing():
216 """ Test command-line completion when indexing on objects.
217 """
218 f = TestPrefilterFrontEnd()
219 f.input_buffer = 'a = [0]'
220 f._on_enter()
221 f.input_buffer = 'a[0].'
222 f.complete_current_input()
223 assert_equal(f.input_buffer, 'a[0].__')
224
225
226 @isolate_ipython0
227 def test_completion_equal():
228 """ Test command-line completion when the delimiter is "=", not " ".
229 """
230 f = TestPrefilterFrontEnd()
231 f.input_buffer = 'a=1.'
232 f.complete_current_input()
233 assert_equal(f.input_buffer, 'a=1.__')
234
172
235
173
236
174 if __name__ == '__main__':
237 if __name__ == '__main__':
@@ -177,4 +240,5 b" if __name__ == '__main__':"
177 test_execution()
240 test_execution()
178 test_multiline()
241 test_multiline()
179 test_capture()
242 test_capture()
180 test_completion()
243 test_completion_simple()
244 test_completion_complex()
@@ -16,7 +16,7 b' from cStringIO import StringIO'
16 from time import sleep
16 from time import sleep
17 import sys
17 import sys
18
18
19 from IPython.frontend._process import PipedProcess
19 from IPython.frontend.process import PipedProcess
20 from IPython.testing import decorators as testdec
20 from IPython.testing import decorators as testdec
21
21
22
22
@@ -25,6 +25,8 b' import wx.stc as stc'
25 from wx.py import editwindow
25 from wx.py import editwindow
26 import time
26 import time
27 import sys
27 import sys
28 import string
29
28 LINESEP = '\n'
30 LINESEP = '\n'
29 if sys.platform == 'win32':
31 if sys.platform == 'win32':
30 LINESEP = '\n\r'
32 LINESEP = '\n\r'
@@ -33,20 +35,26 b' import re'
33
35
34 # FIXME: Need to provide an API for non user-generated display on the
36 # FIXME: Need to provide an API for non user-generated display on the
35 # screen: this should not be editable by the user.
37 # screen: this should not be editable by the user.
38 #-------------------------------------------------------------------------------
39 # Constants
40 #-------------------------------------------------------------------------------
41 _COMPLETE_BUFFER_MARKER = 31
42 _ERROR_MARKER = 30
43 _INPUT_MARKER = 29
36
44
37 _DEFAULT_SIZE = 10
45 _DEFAULT_SIZE = 10
38 if sys.platform == 'darwin':
46 if sys.platform == 'darwin':
39 _DEFAULT_SIZE = 12
47 _DEFAULT_SIZE = 12
40
48
41 _DEFAULT_STYLE = {
49 _DEFAULT_STYLE = {
42 'stdout' : 'fore:#0000FF',
50 #background definition
43 'stderr' : 'fore:#007f00',
44 'trace' : 'fore:#FF0000',
45
46 'default' : 'size:%d' % _DEFAULT_SIZE,
51 'default' : 'size:%d' % _DEFAULT_SIZE,
47 'bracegood' : 'fore:#00AA00,back:#000000,bold',
52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
48 'bracebad' : 'fore:#FF0000,back:#000000,bold',
53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
49
54
55 # Edge column: a number of None
56 'edge_column' : -1,
57
50 # properties for the various Python lexer styles
58 # properties for the various Python lexer styles
51 'comment' : 'fore:#007F00',
59 'comment' : 'fore:#007F00',
52 'number' : 'fore:#007F7F',
60 'number' : 'fore:#007F7F',
@@ -57,7 +65,24 b' _DEFAULT_STYLE = {'
57 'tripledouble' : 'fore:#7F0000',
65 'tripledouble' : 'fore:#7F0000',
58 'class' : 'fore:#0000FF,bold,underline',
66 'class' : 'fore:#0000FF,bold,underline',
59 'def' : 'fore:#007F7F,bold',
67 'def' : 'fore:#007F7F,bold',
60 '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',
61 }
86 }
62
87
63 # new style numbers
88 # new style numbers
@@ -69,6 +94,47 b' _TRACE_STYLE = 17'
69 # system colors
94 # system colors
70 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
95 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
71
96
97 # Translation table from ANSI escape sequences to color.
98 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
99 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
100 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
101 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
102 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
103 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
104 '1;34': [12, 'LIGHT BLUE'], '1;35':
105 [13, 'MEDIUM VIOLET RED'],
106 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
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
111 #we define platform specific fonts
112 if wx.Platform == '__WXMSW__':
113 FACES = { 'times': 'Times New Roman',
114 'mono' : 'Courier New',
115 'helv' : 'Arial',
116 'other': 'Comic Sans MS',
117 'size' : 10,
118 'size2': 8,
119 }
120 elif wx.Platform == '__WXMAC__':
121 FACES = { 'times': 'Times New Roman',
122 'mono' : 'Monaco',
123 'helv' : 'Arial',
124 'other': 'Comic Sans MS',
125 'size' : 10,
126 'size2': 8,
127 }
128 else:
129 FACES = { 'times': 'Times',
130 'mono' : 'Courier',
131 'helv' : 'Helvetica',
132 'other': 'new century schoolbook',
133 'size' : 10,
134 'size2': 8,
135 }
136
137
72 #-------------------------------------------------------------------------------
138 #-------------------------------------------------------------------------------
73 # The console widget class
139 # The console widget class
74 #-------------------------------------------------------------------------------
140 #-------------------------------------------------------------------------------
@@ -83,6 +149,9 b' class ConsoleWidget(editwindow.EditWindow):'
83 # stored.
149 # stored.
84 title = 'Console'
150 title = 'Console'
85
151
152 # Last prompt printed
153 last_prompt = ''
154
86 # The buffer being edited.
155 # The buffer being edited.
87 def _set_input_buffer(self, string):
156 def _set_input_buffer(self, string):
88 self.SetSelection(self.current_prompt_pos, self.GetLength())
157 self.SetSelection(self.current_prompt_pos, self.GetLength())
@@ -103,19 +172,11 b' class ConsoleWidget(editwindow.EditWindow):'
103
172
104 # Translation table from ANSI escape sequences to color. Override
173 # Translation table from ANSI escape sequences to color. Override
105 # this to specify your colors.
174 # this to specify your colors.
106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
175 ANSI_STYLES = ANSI_STYLES.copy()
107 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
108 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
109 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
110 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
111 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
112 '1;34': [12, 'LIGHT BLUE'], '1;35':
113 [13, 'MEDIUM VIOLET RED'],
114 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
115
116 # The color of the carret (call _apply_style() after setting)
117 carret_color = 'BLACK'
118
176
177 # Font faces
178 faces = FACES.copy()
179
119 # Store the last time a refresh was done
180 # Store the last time a refresh was done
120 _last_refresh_time = 0
181 _last_refresh_time = 0
121
182
@@ -126,7 +187,11 b' class ConsoleWidget(editwindow.EditWindow):'
126 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
187 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
188 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
189 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
129 self._configure_scintilla()
190 self.configure_scintilla()
191 # Track if 'enter' key as ever been processed
192 # This variable will only be reallowed until key goes up
193 self.enter_catched = False
194 self.current_prompt_pos = 0
130
195
131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
196 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
197 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
@@ -193,8 +258,19 b' class ConsoleWidget(editwindow.EditWindow):'
193 self.current_prompt_pos = self.GetLength()
258 self.current_prompt_pos = self.GetLength()
194 self.current_prompt_line = self.GetCurrentLine()
259 self.current_prompt_line = self.GetCurrentLine()
195 self.EnsureCaretVisible()
260 self.EnsureCaretVisible()
261 self.last_prompt = prompt
196
262
197
263
264 def continuation_prompt(self):
265 """ Returns the current continuation prompt.
266 We need to implement this method here to deal with the
267 ascii escape sequences cleaning up.
268 """
269 # ASCII-less prompt
270 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
271 return "."*(len(ascii_less)-2) + ': '
272
273
198 def scroll_to_bottom(self):
274 def scroll_to_bottom(self):
199 maxrange = self.GetScrollRange(wx.VERTICAL)
275 maxrange = self.GetScrollRange(wx.VERTICAL)
200 self.ScrollLines(maxrange)
276 self.ScrollLines(maxrange)
@@ -216,37 +292,24 b' class ConsoleWidget(editwindow.EditWindow):'
216 """
292 """
217 return self.GetSize()[0]/self.GetCharWidth()
293 return self.GetSize()[0]/self.GetCharWidth()
218
294
219 #--------------------------------------------------------------------------
220 # EditWindow API
221 #--------------------------------------------------------------------------
222
295
223 def OnUpdateUI(self, event):
296 def configure_scintilla(self):
224 """ Override the OnUpdateUI of the EditWindow class, to prevent
297 """ Set up all the styling option of the embedded scintilla
225 syntax highlighting both for faster redraw, and for more
298 widget.
226 consistent look and feel.
227 """
299 """
300 p = self.style.copy()
301
302 # Marker for complete buffer.
303 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
304 background=p['trace'])
228
305
229 #--------------------------------------------------------------------------
306 # Marker for current input buffer.
230 # Private API
307 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
231 #--------------------------------------------------------------------------
308 background=p['stdout'])
232
309 # Marker for tracebacks.
233 def _apply_style(self):
310 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
234 """ Applies the colors for the different text elements and the
311 background=p['stderr'])
235 carret.
236 """
237 self.SetCaretForeground(self.carret_color)
238
239 #self.StyleClearAll()
240 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
241 "fore:#FF0000,back:#0000FF,bold")
242 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
243 "fore:#000000,back:#FF0000,bold")
244
245 for style in self.ANSI_STYLES.values():
246 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
247
312
248
249 def _configure_scintilla(self):
250 self.SetEOLMode(stc.STC_EOL_LF)
313 self.SetEOLMode(stc.STC_EOL_LF)
251
314
252 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
315 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
@@ -268,7 +331,9 b' class ConsoleWidget(editwindow.EditWindow):'
268 self.SetWrapMode(stc.STC_WRAP_CHAR)
331 self.SetWrapMode(stc.STC_WRAP_CHAR)
269 self.SetWrapMode(stc.STC_WRAP_WORD)
332 self.SetWrapMode(stc.STC_WRAP_WORD)
270 self.SetBufferedDraw(True)
333 self.SetBufferedDraw(True)
271 self.SetUseAntiAliasing(True)
334
335 self.SetUseAntiAliasing(p['antialiasing'])
336
272 self.SetLayoutCache(stc.STC_CACHE_PAGE)
337 self.SetLayoutCache(stc.STC_CACHE_PAGE)
273 self.SetUndoCollection(False)
338 self.SetUndoCollection(False)
274 self.SetUseTabs(True)
339 self.SetUseTabs(True)
@@ -289,23 +354,48 b' class ConsoleWidget(editwindow.EditWindow):'
289 self.SetMarginWidth(1, 0)
354 self.SetMarginWidth(1, 0)
290 self.SetMarginWidth(2, 0)
355 self.SetMarginWidth(2, 0)
291
356
292 self._apply_style()
293
294 # Xterm escape sequences
357 # Xterm escape sequences
295 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
358 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
296 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
359 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
297
360
298 #self.SetEdgeMode(stc.STC_EDGE_LINE)
299 #self.SetEdgeColumn(80)
300
301 # styles
361 # styles
302 p = self.style
362
303 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
363 self.SetCaretForeground(p['carret_color'])
364
365 background_color = p['background_color']
366
367 if 'default' in p:
368 if 'back' not in p['default']:
369 p['default'] += ',back:%s' % background_color
370 if 'size' not in p['default']:
371 p['default'] += ',size:%s' % self.faces['size']
372 if 'face' not in p['default']:
373 p['default'] += ',face:%s' % self.faces['mono']
374
375 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
376 else:
377 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
378 "fore:%s,back:%s,size:%d,face:%s"
379 % (self.ANSI_STYLES['0;30'][1],
380 background_color,
381 self.faces['size'], self.faces['mono']))
382
304 self.StyleClearAll()
383 self.StyleClearAll()
384
385 # XXX: two lines below are usefull if not using the lexer
386 #for style in self.ANSI_STYLES.values():
387 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
388
389 # prompt definition
390 self.prompt_in1 = p['prompt_in1']
391 self.prompt_out = p['prompt_out']
392
393 self.output_prompt_template = string.Template(self.prompt_out)
394 self.input_prompt_template = string.Template(self.prompt_in1)
395
305 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
396 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
306 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
397 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
307 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
398 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
308
309 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
399 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
310 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
400 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
311 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
401 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
@@ -321,6 +411,28 b' class ConsoleWidget(editwindow.EditWindow):'
321 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
411 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
322 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
412 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
323
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)
419
420
421 #--------------------------------------------------------------------------
422 # EditWindow API
423 #--------------------------------------------------------------------------
424
425 def OnUpdateUI(self, event):
426 """ Override the OnUpdateUI of the EditWindow class, to prevent
427 syntax highlighting both for faster redraw, and for more
428 consistent look and feel.
429 """
430
431
432 #--------------------------------------------------------------------------
433 # Private API
434 #--------------------------------------------------------------------------
435
324 def _on_key_down(self, event, skip=True):
436 def _on_key_down(self, event, skip=True):
325 """ Key press callback used for correcting behavior for
437 """ Key press callback used for correcting behavior for
326 console-like interfaces: the cursor is constraint to be after
438 console-like interfaces: the cursor is constraint to be after
@@ -329,6 +441,11 b' class ConsoleWidget(editwindow.EditWindow):'
329 Return True if event as been catched.
441 Return True if event as been catched.
330 """
442 """
331 catched = True
443 catched = True
444 # XXX: Would the right way to do this be to have a
445 # dictionary at the instance level associating keys with
446 # callbacks? How would we deal with inheritance? And Do the
447 # different callbacks share local variables?
448
332 # Intercept some specific keys.
449 # Intercept some specific keys.
333 if event.KeyCode == ord('L') and event.ControlDown() :
450 if event.KeyCode == ord('L') and event.ControlDown() :
334 self.scroll_to_bottom()
451 self.scroll_to_bottom()
@@ -346,6 +463,10 b' class ConsoleWidget(editwindow.EditWindow):'
346 self.ScrollPages(-1)
463 self.ScrollPages(-1)
347 elif event.KeyCode == wx.WXK_PAGEDOWN:
464 elif event.KeyCode == wx.WXK_PAGEDOWN:
348 self.ScrollPages(1)
465 self.ScrollPages(1)
466 elif event.KeyCode == wx.WXK_HOME:
467 self.GotoPos(self.GetLength())
468 elif event.KeyCode == wx.WXK_END:
469 self.GotoPos(self.GetLength())
349 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
470 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
350 self.ScrollLines(-1)
471 self.ScrollLines(-1)
351 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
472 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
@@ -357,16 +478,20 b' class ConsoleWidget(editwindow.EditWindow):'
357 event.Skip()
478 event.Skip()
358 else:
479 else:
359 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
480 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
360 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
481 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
482 wx.MOD_SHIFT):
361 catched = True
483 catched = True
362 self.CallTipCancel()
484 if not self.enter_catched:
363 self.write('\n', refresh=False)
485 self.CallTipCancel()
364 # Under windows scintilla seems to be doing funny stuff to the
486 if event.Modifiers == wx.MOD_SHIFT:
365 # line returns here, but the getter for input_buffer filters
487 # Try to force execution
366 # this out.
488 self.GotoPos(self.GetLength())
367 if sys.platform == 'win32':
489 self.write('\n' + self.continuation_prompt(),
368 self.input_buffer = self.input_buffer
490 refresh=False)
369 self._on_enter()
491 self._on_enter()
492 else:
493 self._on_enter()
494 self.enter_catched = True
370
495
371 elif event.KeyCode == wx.WXK_HOME:
496 elif event.KeyCode == wx.WXK_HOME:
372 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
497 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
@@ -391,16 +516,28 b' class ConsoleWidget(editwindow.EditWindow):'
391 catched = True
516 catched = True
392
517
393 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
518 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
394 if self.GetCurrentPos() > self.current_prompt_pos:
519 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
520 event.Skip()
521 catched = True
522
523 elif event.KeyCode == wx.WXK_RIGHT:
524 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
525 event.Skip()
526 catched = True
527
528
529 elif event.KeyCode == wx.WXK_DELETE:
530 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
395 event.Skip()
531 event.Skip()
396 catched = True
532 catched = True
397
533
398 if skip and not catched:
534 if skip and not catched:
399 # Put the cursor back in the edit region
535 # Put the cursor back in the edit region
400 if self.GetCurrentPos() < self.current_prompt_pos:
536 if not self._keep_cursor_in_buffer():
401 self.GotoPos(self.current_prompt_pos)
537 if not (self.GetCurrentPos() == self.GetLength()
402 else:
538 and event.KeyCode == wx.WXK_DELETE):
403 event.Skip()
539 event.Skip()
540 catched = True
404
541
405 return catched
542 return catched
406
543
@@ -408,17 +545,69 b' class ConsoleWidget(editwindow.EditWindow):'
408 def _on_key_up(self, event, skip=True):
545 def _on_key_up(self, event, skip=True):
409 """ If cursor is outside the editing region, put it back.
546 """ If cursor is outside the editing region, put it back.
410 """
547 """
411 event.Skip()
548 if skip:
412 if self.GetCurrentPos() < self.current_prompt_pos:
549 event.Skip()
413 self.GotoPos(self.current_prompt_pos)
550 self._keep_cursor_in_buffer()
551
552
553 # XXX: I need to avoid the problem of having an empty glass;
554 def _keep_cursor_in_buffer(self, pos=None):
555 """ Checks if the cursor is where it is allowed to be. If not,
556 put it back.
414
557
558 Returns
559 -------
560 cursor_moved: Boolean
561 whether or not the cursor was moved by this routine.
562
563 Notes
564 ------
565 WARNING: This does proper checks only for horizontal
566 movements.
567 """
568 if pos is None:
569 current_pos = self.GetCurrentPos()
570 else:
571 current_pos = pos
572 if current_pos < self.current_prompt_pos:
573 self.GotoPos(self.current_prompt_pos)
574 return True
575 line_num = self.LineFromPosition(current_pos)
576 if not current_pos > self.GetLength():
577 line_pos = self.GetColumn(current_pos)
578 else:
579 line_pos = self.GetColumn(self.GetLength())
580 line = self.GetLine(line_num)
581 # Jump the continuation prompt
582 continuation_prompt = self.continuation_prompt()
583 if ( line.startswith(continuation_prompt)
584 and line_pos < len(continuation_prompt)):
585 if line_pos < 2:
586 # We are at the beginning of the line, trying to move
587 # forward: jump forward.
588 self.GotoPos(current_pos + 1 +
589 len(continuation_prompt) - line_pos)
590 else:
591 # Jump back up
592 self.GotoPos(self.GetLineEndPosition(line_num-1))
593 return True
594 elif ( current_pos > self.GetLineEndPosition(line_num)
595 and not current_pos == self.GetLength()):
596 # Jump to next line
597 self.GotoPos(current_pos + 1 +
598 len(continuation_prompt))
599 return True
600
601 # We re-allow enter event processing
602 self.enter_catched = False
603 return False
415
604
416
605
417 if __name__ == '__main__':
606 if __name__ == '__main__':
418 # Some simple code to test the console widget.
607 # Some simple code to test the console widget.
419 class MainWindow(wx.Frame):
608 class MainWindow(wx.Frame):
420 def __init__(self, parent, id, title):
609 def __init__(self, parent, id, title):
421 wx.Frame.__init__(self, parent, id, title, size=(300,250))
610 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
422 self._sizer = wx.BoxSizer(wx.VERTICAL)
611 self._sizer = wx.BoxSizer(wx.VERTICAL)
423 self.console_widget = ConsoleWidget(self)
612 self.console_widget = ConsoleWidget(self)
424 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
613 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
@@ -80,6 +80,15 b' class IPythonX(wx.Frame):'
80 self.SetSizer(self._sizer)
80 self.SetSizer(self._sizer)
81 self.SetAutoLayout(1)
81 self.SetAutoLayout(1)
82 self.Show(True)
82 self.Show(True)
83 wx.EVT_CLOSE(self, self.on_close)
84
85
86 def on_close(self, event):
87 """ Called on closing the windows.
88
89 Stops the event loop, to close all the child windows.
90 """
91 wx.CallAfter(wx.Exit)
83
92
84
93
85 def main():
94 def main():
@@ -25,38 +25,19 b' __docformat__ = "restructuredtext en"'
25 # Major library imports
25 # Major library imports
26 import re
26 import re
27 import __builtin__
27 import __builtin__
28 from time import sleep
29 import sys
28 import sys
30 from threading import Lock
29 from threading import Lock
31 import string
32
30
33 import wx
31 import wx
34 from wx import stc
32 from wx import stc
35
33
36 # Ipython-specific imports.
34 # Ipython-specific imports.
37 from IPython.frontend._process import PipedProcess
35 from IPython.frontend.process import PipedProcess
38 from console_widget import ConsoleWidget
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 _ERROR_MARKER, _INPUT_MARKER
39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40
39
41 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
42 # Constants
43 #-------------------------------------------------------------------------------
44
45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 _ERROR_BG = '#FFF1F1' # Nice red
48
49 _COMPLETE_BUFFER_MARKER = 31
50 _ERROR_MARKER = 30
51 _INPUT_MARKER = 29
52
53 prompt_in1 = \
54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
55
56 prompt_out = \
57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
58
59 #-------------------------------------------------------------------------------
60 # Classes to implement the Wx frontend
41 # Classes to implement the Wx frontend
61 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
62 class WxController(ConsoleWidget, PrefilterFrontEnd):
43 class WxController(ConsoleWidget, PrefilterFrontEnd):
@@ -66,11 +47,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
66 This class inherits from ConsoleWidget, that provides a console-like
47 This class inherits from ConsoleWidget, that provides a console-like
67 widget to provide a text-rendering widget suitable for a terminal.
48 widget to provide a text-rendering widget suitable for a terminal.
68 """
49 """
69
50
70 output_prompt_template = string.Template(prompt_out)
71
72 input_prompt_template = string.Template(prompt_in1)
73
74 # Print debug info on what is happening to the console.
51 # Print debug info on what is happening to the console.
75 debug = False
52 debug = False
76
53
@@ -138,25 +115,24 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
138 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
139 size=wx.DefaultSize,
116 size=wx.DefaultSize,
140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 styledef=None,
141 *args, **kwds):
119 *args, **kwds):
142 """ Create Shell instance.
120 """ Create Shell instance.
121
122 Parameters
123 -----------
124 styledef : dict, optional
125 styledef is the dictionary of options used to define the
126 style.
143 """
127 """
128 if styledef is not None:
129 self.style = styledef
144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
145 PrefilterFrontEnd.__init__(self, **kwds)
131 PrefilterFrontEnd.__init__(self, **kwds)
146
132
147 # Stick in our own raw_input:
133 # Stick in our own raw_input:
148 self.ipython0.raw_input = self.raw_input
134 self.ipython0.raw_input = self.raw_input
149
135
150 # Marker for complete buffer.
151 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
152 background=_COMPLETE_BUFFER_BG)
153 # Marker for current input buffer.
154 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
155 background=_INPUT_BUFFER_BG)
156 # Marker for tracebacks.
157 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
158 background=_ERROR_BG)
159
160 # A time for flushing the write buffer
136 # A time for flushing the write buffer
161 BUFFER_FLUSH_TIMER_ID = 100
137 BUFFER_FLUSH_TIMER_ID = 100
162 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
@@ -171,8 +147,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
171 self.shell.user_ns['self'] = self
147 self.shell.user_ns['self'] = self
172 # Inject our own raw_input in namespace
148 # Inject our own raw_input in namespace
173 self.shell.user_ns['raw_input'] = self.raw_input
149 self.shell.user_ns['raw_input'] = self.raw_input
174
150
175
176 def raw_input(self, prompt=''):
151 def raw_input(self, prompt=''):
177 """ A replacement from python's raw_input.
152 """ A replacement from python's raw_input.
178 """
153 """
@@ -251,11 +226,8 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
251 if (self.AutoCompActive() and line and not line[-1] == '.') \
226 if (self.AutoCompActive() and line and not line[-1] == '.') \
252 or create==True:
227 or create==True:
253 suggestion, completions = self.complete(line)
228 suggestion, completions = self.complete(line)
254 offset=0
255 if completions:
229 if completions:
256 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
230 offset = len(self._get_completion_text(line))
257 residual = complete_sep.split(line)[-1]
258 offset = len(residual)
259 self.pop_completion(completions, offset=offset)
231 self.pop_completion(completions, offset=offset)
260 if self.debug:
232 if self.debug:
261 print >>sys.__stdout__, completions
233 print >>sys.__stdout__, completions
@@ -276,6 +248,14 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
276 milliseconds=100, oneShot=True)
248 milliseconds=100, oneShot=True)
277
249
278
250
251 def clear_screen(self):
252 """ Empty completely the widget.
253 """
254 self.ClearAll()
255 self.new_prompt(self.input_prompt_template.substitute(
256 number=(self.last_result['number'] + 1)))
257
258
279 #--------------------------------------------------------------------------
259 #--------------------------------------------------------------------------
280 # LineFrontEnd interface
260 # LineFrontEnd interface
281 #--------------------------------------------------------------------------
261 #--------------------------------------------------------------------------
@@ -299,6 +279,41 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
299 raw_string=raw_string)
279 raw_string=raw_string)
300 wx.CallAfter(callback)
280 wx.CallAfter(callback)
301
281
282
283 def execute_command(self, command, hidden=False):
284 """ Execute a command, not only in the model, but also in the
285 view.
286 """
287 # XXX: This method needs to be integrated in the base fronted
288 # interface
289 if hidden:
290 return self.shell.execute(command)
291 else:
292 # XXX: we are not storing the input buffer previous to the
293 # execution, as this forces us to run the execution
294 # input_buffer a yield, which is not good.
295 ##current_buffer = self.shell.control.input_buffer
296 command = command.rstrip()
297 if len(command.split('\n')) > 1:
298 # The input command is several lines long, we need to
299 # force the execution to happen
300 command += '\n'
301 cleaned_command = self.prefilter_input(command)
302 self.input_buffer = command
303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
304 # recursive yields.
305 self.ProcessEvent(wx.PaintEvent())
306 self.write('\n')
307 if not self.is_complete(cleaned_command + '\n'):
308 self._colorize_input_buffer()
309 self.render_error('Incomplete or invalid input')
310 self.new_prompt(self.input_prompt_template.substitute(
311 number=(self.last_result['number'] + 1)))
312 return False
313 self._on_enter()
314 return True
315
316
302 def save_output_hooks(self):
317 def save_output_hooks(self):
303 self.__old_raw_input = __builtin__.raw_input
318 self.__old_raw_input = __builtin__.raw_input
304 PrefilterFrontEnd.save_output_hooks(self)
319 PrefilterFrontEnd.save_output_hooks(self)
@@ -356,10 +371,16 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
356 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
357
372
358
373
374 def continuation_prompt(self, *args, **kwargs):
375 # Avoid multiple inheritence, be explicit about which
376 # parent method class gets called
377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
378
379
359 def write(self, *args, **kwargs):
380 def write(self, *args, **kwargs):
360 # Avoid multiple inheritence, be explicit about which
381 # Avoid multiple inheritence, be explicit about which
361 # parent method class gets called
382 # parent method class gets called
362 ConsoleWidget.write(self, *args, **kwargs)
383 return ConsoleWidget.write(self, *args, **kwargs)
363
384
364
385
365 def _on_key_down(self, event, skip=True):
386 def _on_key_down(self, event, skip=True):
@@ -367,7 +388,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
367 widget handle them, and put our logic afterward.
388 widget handle them, and put our logic afterward.
368 """
389 """
369 # FIXME: This method needs to be broken down in smaller ones.
390 # FIXME: This method needs to be broken down in smaller ones.
370 current_line_number = self.GetCurrentLine()
391 current_line_num = self.GetCurrentLine()
371 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
372 # Capture Control-C
393 # Capture Control-C
373 if self._input_state == 'subprocess':
394 if self._input_state == 'subprocess':
@@ -413,7 +434,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
413 else:
434 else:
414 # Up history
435 # Up history
415 if event.KeyCode == wx.WXK_UP and (
436 if event.KeyCode == wx.WXK_UP and (
416 ( current_line_number == self.current_prompt_line and
437 ( current_line_num == self.current_prompt_line and
417 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
418 or event.ControlDown() ):
439 or event.ControlDown() ):
419 new_buffer = self.get_history_previous(
440 new_buffer = self.get_history_previous(
@@ -425,7 +446,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
425 self.GotoPos(self.current_prompt_pos)
446 self.GotoPos(self.current_prompt_pos)
426 # Down history
447 # Down history
427 elif event.KeyCode == wx.WXK_DOWN and (
448 elif event.KeyCode == wx.WXK_DOWN and (
428 ( current_line_number == self.LineCount -1 and
449 ( current_line_num == self.LineCount -1 and
429 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
430 or event.ControlDown() ):
451 or event.ControlDown() ):
431 new_buffer = self.get_history_next()
452 new_buffer = self.get_history_next()
@@ -433,15 +454,43 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
433 self.input_buffer = new_buffer
454 self.input_buffer = new_buffer
434 # Tab-completion
455 # Tab-completion
435 elif event.KeyCode == ord('\t'):
456 elif event.KeyCode == ord('\t'):
436 current_line, current_line_number = self.CurLine
457 current_line, current_line_num = self.CurLine
437 if not re.match(r'^\s*$', current_line):
458 if not re.match(r'^\s*$', current_line):
438 self.complete_current_input()
459 self.complete_current_input()
439 if self.AutoCompActive():
460 if self.AutoCompActive():
440 wx.CallAfter(self._popup_completion, create=True)
461 wx.CallAfter(self._popup_completion, create=True)
441 else:
462 else:
442 event.Skip()
463 event.Skip()
464 elif event.KeyCode == wx.WXK_BACK:
465 # If characters where erased, check if we have to
466 # remove a line.
467 # XXX: What about DEL?
468 # FIXME: This logics should be in ConsoleWidget, as it is
469 # independant of IPython
470 current_line, _ = self.CurLine
471 current_pos = self.GetCurrentPos()
472 current_line_num = self.LineFromPosition(current_pos)
473 current_col = self.GetColumn(current_pos)
474 len_prompt = len(self.continuation_prompt())
475 if ( current_line.startswith(self.continuation_prompt())
476 and current_col == len_prompt):
477 new_lines = []
478 for line_num, line in enumerate(
479 self.input_buffer.split('\n')):
480 if (line_num + self.current_prompt_line ==
481 current_line_num):
482 new_lines.append(line[len_prompt:])
483 else:
484 new_lines.append('\n'+line)
485 # The first character is '\n', due to the above
486 # code:
487 self.input_buffer = ''.join(new_lines)[1:]
488 self.GotoPos(current_pos - 1 - len_prompt)
489 else:
490 ConsoleWidget._on_key_down(self, event, skip=skip)
443 else:
491 else:
444 ConsoleWidget._on_key_down(self, event, skip=skip)
492 ConsoleWidget._on_key_down(self, event, skip=skip)
493
445
494
446
495
447 def _on_key_up(self, event, skip=True):
496 def _on_key_up(self, event, skip=True):
@@ -453,14 +502,40 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
453 wx.CallAfter(self._popup_completion, create=True)
502 wx.CallAfter(self._popup_completion, create=True)
454 else:
503 else:
455 ConsoleWidget._on_key_up(self, event, skip=skip)
504 ConsoleWidget._on_key_up(self, event, skip=skip)
505 # Make sure the continuation_prompts are always followed by a
506 # whitespace
507 new_lines = []
508 if self._input_state == 'readline':
509 position = self.GetCurrentPos()
510 continuation_prompt = self.continuation_prompt()[:-1]
511 for line in self.input_buffer.split('\n'):
512 if not line == continuation_prompt:
513 new_lines.append(line)
514 self.input_buffer = '\n'.join(new_lines)
515 self.GotoPos(position)
456
516
457
517
458 def _on_enter(self):
518 def _on_enter(self):
459 """ Called on return key down, in readline input_state.
519 """ Called on return key down, in readline input_state.
460 """
520 """
521 last_line_num = self.LineFromPosition(self.GetLength())
522 current_line_num = self.LineFromPosition(self.GetCurrentPos())
523 new_line_pos = (last_line_num - current_line_num)
461 if self.debug:
524 if self.debug:
462 print >>sys.__stdout__, repr(self.input_buffer)
525 print >>sys.__stdout__, repr(self.input_buffer)
463 PrefilterFrontEnd._on_enter(self)
526 self.write('\n', refresh=False)
527 # Under windows scintilla seems to be doing funny
528 # stuff to the line returns here, but the getter for
529 # input_buffer filters this out.
530 if sys.platform == 'win32':
531 self.input_buffer = self.input_buffer
532 old_prompt_num = self.current_prompt_pos
533 has_executed = PrefilterFrontEnd._on_enter(self,
534 new_line_pos=new_line_pos)
535 if old_prompt_num == self.current_prompt_pos:
536 # No execution has happened
537 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
538 return has_executed
464
539
465
540
466 #--------------------------------------------------------------------------
541 #--------------------------------------------------------------------------
@@ -710,8 +710,11 b' class Interpreter(object):'
710 # None does the job nicely.
710 # None does the job nicely.
711 linenos.append(None)
711 linenos.append(None)
712
712
713 # This is causing problems with commands that have a \n embedded in
713 # Same problem at the other end: sometimes the ast tree has its
714 # a string, such as str("""a\nb""")
714 # first complete statement not starting on line 0. In this case
715 # we might miss part of it. This fixes ticket 266993. Thanks Gael!
716 linenos[0] = 0
717
715 lines = python.splitlines()
718 lines = python.splitlines()
716
719
717 # Create a list of atomic commands.
720 # Create a list of atomic commands.
@@ -5,12 +5,12 b''
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
@@ -18,19 +18,44 b' __docformat__ = "restructuredtext en"'
18 import unittest
18 import unittest
19 from IPython.kernel.core.interpreter import Interpreter
19 from IPython.kernel.core.interpreter import Interpreter
20
20
21 #-----------------------------------------------------------------------------
22 # Tests
23 #-----------------------------------------------------------------------------
24
21 class TestInterpreter(unittest.TestCase):
25 class TestInterpreter(unittest.TestCase):
22
26
23 def test_unicode(self):
27 def test_unicode(self):
24 """ Test unicode handling with the interpreter.
28 """ Test unicode handling with the interpreter."""
25 """
26 i = Interpreter()
29 i = Interpreter()
27 i.execute_python(u'print "ù"')
30 i.execute_python(u'print "ù"')
28 i.execute_python('print "ù"')
31 i.execute_python('print "ù"')
29
32
30 def test_ticket266993_1(self):
33 def test_ticket266993_1(self):
34 """ Test for ticket 266993."""
31 i = Interpreter()
35 i = Interpreter()
32 i.execute('str("""a\nb""")')
36 i.execute('str("""a\nb""")')
33
37
34 def test_ticket266993_2(self):
38 def test_ticket364347(self):
39 """Test for ticket 364347."""
35 i = Interpreter()
40 i = Interpreter()
36 i.execute('str("a\nb")') No newline at end of file
41 i.split_commands('str("a\nb")')
42
43 def test_split_commands(self):
44 """ Test that commands are indeed individually split."""
45 i = Interpreter()
46 test_atoms = [('(1\n + 1)', ),
47 ('1', '1', ),
48 ]
49 for atoms in test_atoms:
50 atoms = [atom.rstrip() + '\n' for atom in atoms]
51 self.assertEquals(i.split_commands(''.join(atoms)),atoms)
52
53 def test_long_lines(self):
54 """ Test for spurious syntax error created by the interpreter."""
55 test_strings = [u'( 1 +\n 1\n )\n\n',
56 u'(1 \n + 1\n )\n\n',
57 ]
58 i = Interpreter()
59 for s in test_strings:
60 i.execute(s)
61
@@ -109,7 +109,7 b' def find_packages():'
109 add_package(packages, 'gui')
109 add_package(packages, 'gui')
110 add_package(packages, 'gui.wx')
110 add_package(packages, 'gui.wx')
111 add_package(packages, 'frontend', tests=True)
111 add_package(packages, 'frontend', tests=True)
112 add_package(packages, 'frontend._process')
112 add_package(packages, 'frontend.process')
113 add_package(packages, 'frontend.wx')
113 add_package(packages, 'frontend.wx')
114 add_package(packages, 'frontend.cocoa', tests=True)
114 add_package(packages, 'frontend.cocoa', tests=True)
115 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
115 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
General Comments 0
You need to be logged in to leave comments. Login now