##// 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 19 import re
20 20
21 import IPython
22 21 import sys
23 22 import codeop
24 import traceback
25 23
26 24 from frontendbase import FrontEndBase
27 25 from IPython.kernel.core.interpreter import Interpreter
@@ -58,6 +56,9 b' class LineFrontEndBase(FrontEndBase):'
58 56 # programatic control of the frontend.
59 57 last_result = dict(number=0)
60 58
59 # The last prompt displayed. Useful for continuation prompts.
60 last_prompt = ''
61
61 62 # The input buffer being edited
62 63 input_buffer = ''
63 64
@@ -151,8 +152,12 b' class LineFrontEndBase(FrontEndBase):'
151 152 self.capture_output()
152 153 try:
153 154 # Add line returns here, to make sure that the statement is
154 # complete.
155 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
155 # complete (except if '\' was used).
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 161 "<string>", "exec")
157 162 self.release_output()
158 163 except Exception, e:
@@ -183,16 +188,6 b' class LineFrontEndBase(FrontEndBase):'
183 188 # Create a false result, in case there is an exception
184 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 191 try:
197 192 try:
198 193 self.history.input_cache[-1] = raw_string.rstrip()
@@ -272,15 +267,15 b' class LineFrontEndBase(FrontEndBase):'
272 267 symbols_per_line = max(1, chars_per_line/max_len)
273 268
274 269 pos = 1
275 buf = []
270 completion_string = []
276 271 for symbol in possibilities:
277 272 if pos < symbols_per_line:
278 buf.append(symbol.ljust(max_len))
273 completion_string.append(symbol.ljust(max_len))
279 274 pos += 1
280 275 else:
281 buf.append(symbol.rstrip() + '\n')
276 completion_string.append(symbol.rstrip() + '\n')
282 277 pos = 1
283 self.write(''.join(buf))
278 self.write(''.join(completion_string))
284 279 self.new_prompt(self.input_prompt_template.substitute(
285 280 number=self.last_result['number'] + 1))
286 281 self.input_buffer = new_line
@@ -297,26 +292,70 b' class LineFrontEndBase(FrontEndBase):'
297 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 308 # Private API
302 309 #--------------------------------------------------------------------------
303 310
304 def _on_enter(self):
311 def _on_enter(self, new_line_pos=0):
305 312 """ Called when the return key is pressed in a line editing
306 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 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 333 if self.is_complete(cleaned_buffer):
311 334 self.execute(cleaned_buffer, raw_string=current_buffer)
335 return True
312 336 else:
313 self.input_buffer += self._get_indent_string(
314 current_buffer[:-1])
315 if len(current_buffer.split('\n')) == 2:
316 self.input_buffer += '\t\t'
317 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
318 self.input_buffer += '\t'
319
337 # Start a new line.
338 new_line_pos = -new_line_pos
339 lines = current_buffer.split('\n')[:-1]
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.
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 360 def _get_indent_string(self, string):
322 361 """ Return the string of whitespace that prefixes a line. Used to
@@ -22,9 +22,10 b' __docformat__ = "restructuredtext en"'
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24 import sys
25
26 from linefrontendbase import LineFrontEndBase, common_prefix
27 from frontendbase import FrontEndBase
25 import pydoc
26 import os
27 import re
28 import __builtin__
28 29
29 30 from IPython.ipmaker import make_IPython
30 31 from IPython.ipapi import IPApi
@@ -33,9 +34,8 b' from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap'
33 34 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34 35
35 36 from IPython.genutils import Term
36 import pydoc
37 import os
38 import sys
37
38 from linefrontendbase import LineFrontEndBase, common_prefix
39 39
40 40
41 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 46 def my_system_call(args):
47 47 system_call_function("%s %s" % (command, args))
48
49 my_system_call.__doc__ = "Calls %s" % command
48 50 return my_system_call
49 51
50 52 #-------------------------------------------------------------------------------
@@ -69,6 +71,13 b' class PrefilterFrontEnd(LineFrontEndBase):'
69 71 ipython0: an optional ipython0 instance to use for command
70 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 81 LineFrontEndBase.__init__(self, *args, **kwargs)
73 82 self.shell.output_trap = RedirectorOutputTrap(
74 83 out_callback=self.write,
@@ -83,10 +92,16 b' class PrefilterFrontEnd(LineFrontEndBase):'
83 92 if ipython0 is None:
84 93 # Instanciate an IPython0 interpreter to be able to use the
85 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 100 # XXX: argv=[] is a bit bold.
87 101 ipython0 = make_IPython(argv=[],
88 102 user_ns=self.shell.user_ns,
89 103 user_global_ns=self.shell.user_global_ns)
104 __builtin__.raw_input = old_rawinput
90 105 self.ipython0 = ipython0
91 106 # Set the pager:
92 107 self.ipython0.set_hook('show_in_pager',
@@ -98,16 +113,17 b' class PrefilterFrontEnd(LineFrontEndBase):'
98 113 self._ip.system = self.system_call
99 114 # XXX: Muck around with magics so that they work better
100 115 # in our environment
101 self.ipython0.magic_ls = mk_system_call(self.system_call,
102 'ls -CF')
116 if not sys.platform.startswith('win'):
117 self.ipython0.magic_ls = mk_system_call(self.system_call,
118 'ls -CF')
103 119 # And now clean up the mess created by ipython0
104 120 self.release_output()
105 121
106 122
107 123 if not 'banner' in kwargs and self.banner is None:
108 self.banner = self.ipython0.BANNER + """
109 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
124 self.banner = self.ipython0.BANNER
110 125
126 # FIXME: __init__ and start should be two different steps
111 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 133 def show_traceback(self):
118 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 140 self.ipython0.showtraceback(tb_offset=-1)
122 141 self.release_output()
123 142
@@ -171,7 +190,7 b' This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""'
171 190 def complete(self, line):
172 191 # FIXME: This should be factored out in the linefrontendbase
173 192 # method.
174 word = line.split('\n')[-1].split(' ')[-1]
193 word = self._get_completion_text(line)
175 194 completions = self.ipython0.complete(word)
176 195 # FIXME: The proper sort should be done in the complete method.
177 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 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
@@ -151,7 +151,12 b' else:'
151 151 self._thread = ht
152 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 160 winprocess.ResumeThread(ht)
156 161
157 162 if p2cread is not None:
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
@@ -15,9 +15,27 b' __docformat__ = "restructuredtext en"'
15 15 from cStringIO import StringIO
16 16 import string
17 17
18 from nose.tools import assert_equal
19
18 20 from IPython.ipapi import get as get_ipython0
19 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 40 class TestPrefilterFrontEnd(PrefilterFrontEnd):
23 41
@@ -26,16 +44,8 b' class TestPrefilterFrontEnd(PrefilterFrontEnd):'
26 44 banner = ''
27 45
28 46 def __init__(self):
29 ipython0 = get_ipython0().IP
30 47 self.out = StringIO()
31 PrefilterFrontEnd.__init__(self, ipython0=ipython0)
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)
48 PrefilterFrontEnd.__init__(self)
39 49 # Some more code for isolation (yeah, crazy)
40 50 self._on_enter()
41 51 self.out.flush()
@@ -52,17 +62,31 b' class TestPrefilterFrontEnd(PrefilterFrontEnd):'
52 62
53 63 def isolate_ipython0(func):
54 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):
57 ipython0 = get_ipython0().IP
58 user_ns = deepcopy(ipython0.user_ns)
59 global_ns = deepcopy(ipython0.global_ns)
72 def my_func():
73 iplib = get_ipython0()
74 if iplib is None:
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 79 try:
61 func(*args, **kwargs)
80 out = func()
62 81 finally:
63 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 90 return my_func
67 91
68 92
@@ -74,7 +98,7 b' def test_execution():'
74 98 f.input_buffer = 'print 1'
75 99 f._on_enter()
76 100 out_value = f.out.getvalue()
77 assert out_value == '1\n'
101 assert_equal(out_value, '1\n')
78 102
79 103
80 104 @isolate_ipython0
@@ -87,20 +111,20 b' def test_multiline():'
87 111 f.input_buffer += 'print 1'
88 112 f._on_enter()
89 113 out_value = f.out.getvalue()
90 assert out_value == ''
114 yield assert_equal, out_value, ''
91 115 f._on_enter()
92 116 out_value = f.out.getvalue()
93 assert out_value == '1\n'
117 yield assert_equal, out_value, '1\n'
94 118 f = TestPrefilterFrontEnd()
95 119 f.input_buffer='(1 +'
96 120 f._on_enter()
97 121 f.input_buffer += '0)'
98 122 f._on_enter()
99 123 out_value = f.out.getvalue()
100 assert out_value == ''
124 yield assert_equal, out_value, ''
101 125 f._on_enter()
102 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 130 @isolate_ipython0
@@ -113,13 +137,13 b' def test_capture():'
113 137 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
114 138 f._on_enter()
115 139 out_value = f.out.getvalue()
116 assert out_value == '1'
140 yield assert_equal, out_value, '1'
117 141 f = TestPrefilterFrontEnd()
118 142 f.input_buffer = \
119 143 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
120 144 f._on_enter()
121 145 out_value = f.out.getvalue()
122 assert out_value == '1'
146 yield assert_equal, out_value, '1'
123 147
124 148
125 149 @isolate_ipython0
@@ -132,7 +156,7 b' def test_magic():'
132 156 f.input_buffer += '%who'
133 157 f._on_enter()
134 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 162 @isolate_ipython0
@@ -156,8 +180,8 b' def test_help():'
156 180
157 181
158 182 @isolate_ipython0
159 def test_completion():
160 """ Test command-line completion.
183 def test_completion_simple():
184 """ Test command-line completion on trivial examples.
161 185 """
162 186 f = TestPrefilterFrontEnd()
163 187 f.input_buffer = 'zzza = 1'
@@ -167,8 +191,47 b' def test_completion():'
167 191 f.input_buffer = 'zz'
168 192 f.complete_current_input()
169 193 out_value = f.out.getvalue()
170 assert out_value == '\nzzza zzzb '
171 assert f.input_buffer == 'zzz'
194 yield assert_equal, out_value, '\nzzza zzzb '
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 237 if __name__ == '__main__':
@@ -177,4 +240,5 b" if __name__ == '__main__':"
177 240 test_execution()
178 241 test_multiline()
179 242 test_capture()
180 test_completion()
243 test_completion_simple()
244 test_completion_complex()
@@ -16,7 +16,7 b' 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
@@ -25,6 +25,8 b' import wx.stc as stc'
25 25 from wx.py import editwindow
26 26 import time
27 27 import sys
28 import string
29
28 30 LINESEP = '\n'
29 31 if sys.platform == 'win32':
30 32 LINESEP = '\n\r'
@@ -33,20 +35,26 b' import re'
33 35
34 36 # FIXME: Need to provide an API for non user-generated display on the
35 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 45 _DEFAULT_SIZE = 10
38 46 if sys.platform == 'darwin':
39 47 _DEFAULT_SIZE = 12
40 48
41 49 _DEFAULT_STYLE = {
42 'stdout' : 'fore:#0000FF',
43 'stderr' : 'fore:#007f00',
44 'trace' : 'fore:#FF0000',
45
50 #background definition
46 51 'default' : 'size:%d' % _DEFAULT_SIZE,
47 52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
48 53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
49 54
55 # Edge column: a number of None
56 'edge_column' : -1,
57
50 58 # properties for the various Python lexer styles
51 59 'comment' : 'fore:#007F00',
52 60 'number' : 'fore:#007F7F',
@@ -57,7 +65,24 b' _DEFAULT_STYLE = {'
57 65 'tripledouble' : 'fore:#7F0000',
58 66 'class' : 'fore:#0000FF,bold,underline',
59 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 88 # new style numbers
@@ -69,6 +94,47 b' _TRACE_STYLE = 17'
69 94 # system colors
70 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 139 # The console widget class
74 140 #-------------------------------------------------------------------------------
@@ -83,6 +149,9 b' class ConsoleWidget(editwindow.EditWindow):'
83 149 # stored.
84 150 title = 'Console'
85 151
152 # Last prompt printed
153 last_prompt = ''
154
86 155 # The buffer being edited.
87 156 def _set_input_buffer(self, string):
88 157 self.SetSelection(self.current_prompt_pos, self.GetLength())
@@ -103,19 +172,11 b' class ConsoleWidget(editwindow.EditWindow):'
103 172
104 173 # Translation table from ANSI escape sequences to color. Override
105 174 # this to specify your colors.
106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
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'
175 ANSI_STYLES = ANSI_STYLES.copy()
118 176
177 # Font faces
178 faces = FACES.copy()
179
119 180 # Store the last time a refresh was done
120 181 _last_refresh_time = 0
121 182
@@ -126,7 +187,11 b' class ConsoleWidget(editwindow.EditWindow):'
126 187 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
127 188 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 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 196 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 197 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
@@ -193,8 +258,19 b' class ConsoleWidget(editwindow.EditWindow):'
193 258 self.current_prompt_pos = self.GetLength()
194 259 self.current_prompt_line = self.GetCurrentLine()
195 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 274 def scroll_to_bottom(self):
199 275 maxrange = self.GetScrollRange(wx.VERTICAL)
200 276 self.ScrollLines(maxrange)
@@ -216,37 +292,24 b' class ConsoleWidget(editwindow.EditWindow):'
216 292 """
217 293 return self.GetSize()[0]/self.GetCharWidth()
218 294
219 #--------------------------------------------------------------------------
220 # EditWindow API
221 #--------------------------------------------------------------------------
222 295
223 def OnUpdateUI(self, event):
224 """ Override the OnUpdateUI of the EditWindow class, to prevent
225 syntax highlighting both for faster redraw, and for more
226 consistent look and feel.
296 def configure_scintilla(self):
297 """ Set up all the styling option of the embedded scintilla
298 widget.
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 #--------------------------------------------------------------------------
230 # Private API
231 #--------------------------------------------------------------------------
232
233 def _apply_style(self):
234 """ Applies the colors for the different text elements and the
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])
306 # Marker for current input buffer.
307 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
308 background=p['stdout'])
309 # Marker for tracebacks.
310 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
311 background=p['stderr'])
247 312
248
249 def _configure_scintilla(self):
250 313 self.SetEOLMode(stc.STC_EOL_LF)
251 314
252 315 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
@@ -268,7 +331,9 b' class ConsoleWidget(editwindow.EditWindow):'
268 331 self.SetWrapMode(stc.STC_WRAP_CHAR)
269 332 self.SetWrapMode(stc.STC_WRAP_WORD)
270 333 self.SetBufferedDraw(True)
271 self.SetUseAntiAliasing(True)
334
335 self.SetUseAntiAliasing(p['antialiasing'])
336
272 337 self.SetLayoutCache(stc.STC_CACHE_PAGE)
273 338 self.SetUndoCollection(False)
274 339 self.SetUseTabs(True)
@@ -289,23 +354,48 b' class ConsoleWidget(editwindow.EditWindow):'
289 354 self.SetMarginWidth(1, 0)
290 355 self.SetMarginWidth(2, 0)
291 356
292 self._apply_style()
293
294 357 # Xterm escape sequences
295 358 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
296 359 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
297 360
298 #self.SetEdgeMode(stc.STC_EDGE_LINE)
299 #self.SetEdgeColumn(80)
300
301 361 # styles
302 p = self.style
303 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
362
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 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 396 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
306 397 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
307 398 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
308
309 399 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
310 400 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
311 401 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
@@ -321,6 +411,28 b' class ConsoleWidget(editwindow.EditWindow):'
321 411 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
322 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 436 def _on_key_down(self, event, skip=True):
325 437 """ Key press callback used for correcting behavior for
326 438 console-like interfaces: the cursor is constraint to be after
@@ -329,6 +441,11 b' class ConsoleWidget(editwindow.EditWindow):'
329 441 Return True if event as been catched.
330 442 """
331 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 449 # Intercept some specific keys.
333 450 if event.KeyCode == ord('L') and event.ControlDown() :
334 451 self.scroll_to_bottom()
@@ -346,6 +463,10 b' class ConsoleWidget(editwindow.EditWindow):'
346 463 self.ScrollPages(-1)
347 464 elif event.KeyCode == wx.WXK_PAGEDOWN:
348 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 470 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
350 471 self.ScrollLines(-1)
351 472 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
@@ -357,16 +478,20 b' class ConsoleWidget(editwindow.EditWindow):'
357 478 event.Skip()
358 479 else:
359 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 483 catched = True
362 self.CallTipCancel()
363 self.write('\n', refresh=False)
364 # Under windows scintilla seems to be doing funny stuff to the
365 # line returns here, but the getter for input_buffer filters
366 # this out.
367 if sys.platform == 'win32':
368 self.input_buffer = self.input_buffer
369 self._on_enter()
484 if not self.enter_catched:
485 self.CallTipCancel()
486 if event.Modifiers == wx.MOD_SHIFT:
487 # Try to force execution
488 self.GotoPos(self.GetLength())
489 self.write('\n' + self.continuation_prompt(),
490 refresh=False)
491 self._on_enter()
492 else:
493 self._on_enter()
494 self.enter_catched = True
370 495
371 496 elif event.KeyCode == wx.WXK_HOME:
372 497 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
@@ -391,16 +516,28 b' class ConsoleWidget(editwindow.EditWindow):'
391 516 catched = True
392 517
393 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 531 event.Skip()
396 532 catched = True
397 533
398 534 if skip and not catched:
399 535 # Put the cursor back in the edit region
400 if self.GetCurrentPos() < self.current_prompt_pos:
401 self.GotoPos(self.current_prompt_pos)
402 else:
403 event.Skip()
536 if not self._keep_cursor_in_buffer():
537 if not (self.GetCurrentPos() == self.GetLength()
538 and event.KeyCode == wx.WXK_DELETE):
539 event.Skip()
540 catched = True
404 541
405 542 return catched
406 543
@@ -408,17 +545,69 b' class ConsoleWidget(editwindow.EditWindow):'
408 545 def _on_key_up(self, event, skip=True):
409 546 """ If cursor is outside the editing region, put it back.
410 547 """
411 event.Skip()
412 if self.GetCurrentPos() < self.current_prompt_pos:
413 self.GotoPos(self.current_prompt_pos)
548 if skip:
549 event.Skip()
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 606 if __name__ == '__main__':
418 607 # Some simple code to test the console widget.
419 608 class MainWindow(wx.Frame):
420 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 611 self._sizer = wx.BoxSizer(wx.VERTICAL)
423 612 self.console_widget = ConsoleWidget(self)
424 613 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
@@ -80,6 +80,15 b' class IPythonX(wx.Frame):'
80 80 self.SetSizer(self._sizer)
81 81 self.SetAutoLayout(1)
82 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 94 def main():
@@ -25,38 +25,19 b' __docformat__ = "restructuredtext en"'
25 25 # Major library imports
26 26 import re
27 27 import __builtin__
28 from time import sleep
29 28 import sys
30 29 from threading import Lock
31 import string
32 30
33 31 import wx
34 32 from wx import stc
35 33
36 34 # Ipython-specific imports.
37 from IPython.frontend._process import PipedProcess
38 from console_widget import ConsoleWidget
35 from IPython.frontend.process import PipedProcess
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 _ERROR_MARKER, _INPUT_MARKER
39 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 41 # Classes to implement the Wx frontend
61 42 #-------------------------------------------------------------------------------
62 43 class WxController(ConsoleWidget, PrefilterFrontEnd):
@@ -66,11 +47,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
66 47 This class inherits from ConsoleWidget, that provides a console-like
67 48 widget to provide a text-rendering widget suitable for a terminal.
68 49 """
69
70 output_prompt_template = string.Template(prompt_out)
71
72 input_prompt_template = string.Template(prompt_in1)
73
50
74 51 # Print debug info on what is happening to the console.
75 52 debug = False
76 53
@@ -138,25 +115,24 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
138 115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
139 116 size=wx.DefaultSize,
140 117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 styledef=None,
141 119 *args, **kwds):
142 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 130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
145 131 PrefilterFrontEnd.__init__(self, **kwds)
146 132
147 133 # Stick in our own raw_input:
148 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 136 # A time for flushing the write buffer
161 137 BUFFER_FLUSH_TIMER_ID = 100
162 138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
@@ -171,8 +147,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
171 147 self.shell.user_ns['self'] = self
172 148 # Inject our own raw_input in namespace
173 149 self.shell.user_ns['raw_input'] = self.raw_input
174
175
150
176 151 def raw_input(self, prompt=''):
177 152 """ A replacement from python's raw_input.
178 153 """
@@ -251,11 +226,8 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
251 226 if (self.AutoCompActive() and line and not line[-1] == '.') \
252 227 or create==True:
253 228 suggestion, completions = self.complete(line)
254 offset=0
255 229 if completions:
256 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
257 residual = complete_sep.split(line)[-1]
258 offset = len(residual)
230 offset = len(self._get_completion_text(line))
259 231 self.pop_completion(completions, offset=offset)
260 232 if self.debug:
261 233 print >>sys.__stdout__, completions
@@ -276,6 +248,14 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
276 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 260 # LineFrontEnd interface
281 261 #--------------------------------------------------------------------------
@@ -299,6 +279,41 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
299 279 raw_string=raw_string)
300 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 317 def save_output_hooks(self):
303 318 self.__old_raw_input = __builtin__.raw_input
304 319 PrefilterFrontEnd.save_output_hooks(self)
@@ -356,10 +371,16 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
356 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 380 def write(self, *args, **kwargs):
360 381 # Avoid multiple inheritence, be explicit about which
361 382 # parent method class gets called
362 ConsoleWidget.write(self, *args, **kwargs)
383 return ConsoleWidget.write(self, *args, **kwargs)
363 384
364 385
365 386 def _on_key_down(self, event, skip=True):
@@ -367,7 +388,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
367 388 widget handle them, and put our logic afterward.
368 389 """
369 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 392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
372 393 # Capture Control-C
373 394 if self._input_state == 'subprocess':
@@ -413,7 +434,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
413 434 else:
414 435 # Up history
415 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 438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
418 439 or event.ControlDown() ):
419 440 new_buffer = self.get_history_previous(
@@ -425,7 +446,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
425 446 self.GotoPos(self.current_prompt_pos)
426 447 # Down history
427 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 450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
430 451 or event.ControlDown() ):
431 452 new_buffer = self.get_history_next()
@@ -433,15 +454,43 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
433 454 self.input_buffer = new_buffer
434 455 # Tab-completion
435 456 elif event.KeyCode == ord('\t'):
436 current_line, current_line_number = self.CurLine
457 current_line, current_line_num = self.CurLine
437 458 if not re.match(r'^\s*$', current_line):
438 459 self.complete_current_input()
439 460 if self.AutoCompActive():
440 461 wx.CallAfter(self._popup_completion, create=True)
441 462 else:
442 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 491 else:
444 492 ConsoleWidget._on_key_down(self, event, skip=skip)
493
445 494
446 495
447 496 def _on_key_up(self, event, skip=True):
@@ -453,14 +502,40 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
453 502 wx.CallAfter(self._popup_completion, create=True)
454 503 else:
455 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 518 def _on_enter(self):
459 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 524 if self.debug:
462 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 710 # None does the job nicely.
711 711 linenos.append(None)
712 712
713 # This is causing problems with commands that have a \n embedded in
714 # a string, such as str("""a\nb""")
713 # Same problem at the other end: sometimes the ast tree has its
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 718 lines = python.splitlines()
716 719
717 720 # Create a list of atomic commands.
@@ -5,12 +5,12 b''
5 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 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13
13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
@@ -18,19 +18,44 b' __docformat__ = "restructuredtext en"'
18 18 import unittest
19 19 from IPython.kernel.core.interpreter import Interpreter
20 20
21 #-----------------------------------------------------------------------------
22 # Tests
23 #-----------------------------------------------------------------------------
24
21 25 class TestInterpreter(unittest.TestCase):
22
26
23 27 def test_unicode(self):
24 """ Test unicode handling with the interpreter.
25 """
28 """ Test unicode handling with the interpreter."""
26 29 i = Interpreter()
27 30 i.execute_python(u'print "ù"')
28 31 i.execute_python('print "ù"')
29
32
30 33 def test_ticket266993_1(self):
34 """ Test for ticket 266993."""
31 35 i = Interpreter()
32 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 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 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)
General Comments 0
You need to be logged in to leave comments. Login now