##// END OF EJS Templates
MERGE Laurent's tweaks. Clean up'
Gael Varoquaux -
r1893:c14af28e merge
parent child Browse files
Show More
@@ -1,179 +1,184 b''
1 1 # Addapted from killableprocess.py.
2 2 #______________________________________________________________________________
3 3 #
4 4 # killableprocess - subprocesses which can be reliably killed
5 5 #
6 6 # Parts of this module are copied from the subprocess.py file contained
7 7 # in the Python distribution.
8 8 #
9 9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
10 10 #
11 11 # Additions and modifications written by Benjamin Smedberg
12 12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
13 13 # <http://www.mozilla.org/>
14 14 #
15 15 # By obtaining, using, and/or copying this software and/or its
16 16 # associated documentation, you agree that you have read, understood,
17 17 # and will comply with the following terms and conditions:
18 18 #
19 19 # Permission to use, copy, modify, and distribute this software and
20 20 # its associated documentation for any purpose and without fee is
21 21 # hereby granted, provided that the above copyright notice appears in
22 22 # all copies, and that both that copyright notice and this permission
23 23 # notice appear in supporting documentation, and that the name of the
24 24 # author not be used in advertising or publicity pertaining to
25 25 # distribution of the software without specific, written prior
26 26 # permission.
27 27 #
28 28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
29 29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
30 30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
31 31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
32 32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33 33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34 34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35 35
36 36 r"""killableprocess - Subprocesses which can be reliably killed
37 37
38 38 This module is a subclass of the builtin "subprocess" module. It allows
39 39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
40 40
41 41 It also adds a timeout argument to Wait() for a limited period of time before
42 42 forcefully killing the process.
43 43
44 44 Note: On Windows, this module requires Windows 2000 or higher (no support for
45 45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
46 46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
47 47 """
48 48
49 49 import subprocess
50 50 from subprocess import PIPE
51 51 import sys
52 52 import os
53 53 import types
54 54
55 55 try:
56 56 from subprocess import CalledProcessError
57 57 except ImportError:
58 58 # Python 2.4 doesn't implement CalledProcessError
59 59 class CalledProcessError(Exception):
60 60 """This exception is raised when a process run by check_call() returns
61 61 a non-zero exit status. The exit status will be stored in the
62 62 returncode attribute."""
63 63 def __init__(self, returncode, cmd):
64 64 self.returncode = returncode
65 65 self.cmd = cmd
66 66 def __str__(self):
67 67 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
68 68
69 69 mswindows = (sys.platform == "win32")
70 70
71 71 skip = False
72 72
73 73 if mswindows:
74 74 import platform
75 75 if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000':
76 76 # Killable process does not work under vista when starting for
77 77 # something else than cmd.
78 78 skip = True
79 79 else:
80 80 import winprocess
81 81 else:
82 82 import signal
83 83
84 84 if not mswindows:
85 85 def DoNothing(*args):
86 86 pass
87 87
88 88
89 89 if skip:
90 90 Popen = subprocess.Popen
91 91 else:
92 92 class Popen(subprocess.Popen):
93 93 if not mswindows:
94 94 # Override __init__ to set a preexec_fn
95 95 def __init__(self, *args, **kwargs):
96 96 if len(args) >= 7:
97 97 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
98 98
99 99 real_preexec_fn = kwargs.pop("preexec_fn", None)
100 100 def setpgid_preexec_fn():
101 101 os.setpgid(0, 0)
102 102 if real_preexec_fn:
103 103 apply(real_preexec_fn)
104 104
105 105 kwargs['preexec_fn'] = setpgid_preexec_fn
106 106
107 107 subprocess.Popen.__init__(self, *args, **kwargs)
108 108
109 109 if mswindows:
110 110 def _execute_child(self, args, executable, preexec_fn, close_fds,
111 111 cwd, env, universal_newlines, startupinfo,
112 112 creationflags, shell,
113 113 p2cread, p2cwrite,
114 114 c2pread, c2pwrite,
115 115 errread, errwrite):
116 116 if not isinstance(args, types.StringTypes):
117 117 args = subprocess.list2cmdline(args)
118 118
119 119 if startupinfo is None:
120 120 startupinfo = winprocess.STARTUPINFO()
121 121
122 122 if None not in (p2cread, c2pwrite, errwrite):
123 123 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
124 124
125 125 startupinfo.hStdInput = int(p2cread)
126 126 startupinfo.hStdOutput = int(c2pwrite)
127 127 startupinfo.hStdError = int(errwrite)
128 128 if shell:
129 129 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
130 130 startupinfo.wShowWindow = winprocess.SW_HIDE
131 131 comspec = os.environ.get("COMSPEC", "cmd.exe")
132 132 args = comspec + " /c " + args
133 133
134 134 # We create a new job for this process, so that we can kill
135 135 # the process and any sub-processes
136 136 self._job = winprocess.CreateJobObject()
137 137
138 138 creationflags |= winprocess.CREATE_SUSPENDED
139 139 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
140 140
141 141 hp, ht, pid, tid = winprocess.CreateProcess(
142 142 executable, args,
143 143 None, None, # No special security
144 144 1, # Must inherit handles!
145 145 creationflags,
146 146 winprocess.EnvironmentBlock(env),
147 147 cwd, startupinfo)
148 148
149 149 self._child_created = True
150 150 self._handle = hp
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:
158 163 p2cread.Close()
159 164 if c2pwrite is not None:
160 165 c2pwrite.Close()
161 166 if errwrite is not None:
162 167 errwrite.Close()
163 168
164 169 def kill(self, group=True):
165 170 """Kill the process. If group=True, all sub-processes will also be killed."""
166 171 if mswindows:
167 172 if group:
168 173 winprocess.TerminateJobObject(self._job, 127)
169 174 else:
170 175 winprocess.TerminateProcess(self._handle, 127)
171 176 self.returncode = 127
172 177 else:
173 178 if group:
174 179 os.killpg(self.pid, signal.SIGKILL)
175 180 else:
176 181 os.kill(self.pid, signal.SIGKILL)
177 182 self.returncode = -9
178 183
179 184
@@ -1,357 +1,355 b''
1 1 """
2 2 Base front end class for all line-oriented frontends, rather than
3 3 block-oriented.
4 4
5 5 Currently this focuses on synchronous frontends.
6 6 """
7 7 __docformat__ = "restructuredtext en"
8 8
9 9 #-------------------------------------------------------------------------------
10 10 # Copyright (C) 2008 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-------------------------------------------------------------------------------
15 15
16 16 #-------------------------------------------------------------------------------
17 17 # Imports
18 18 #-------------------------------------------------------------------------------
19 19 import re
20 20
21 21 import sys
22 22 import codeop
23 23
24 24 from frontendbase import FrontEndBase
25 25 from IPython.kernel.core.interpreter import Interpreter
26 26
27 27 def common_prefix(strings):
28 28 """ Given a list of strings, return the common prefix between all
29 29 these strings.
30 30 """
31 31 ref = strings[0]
32 32 prefix = ''
33 33 for size in range(len(ref)):
34 34 test_prefix = ref[:size+1]
35 35 for string in strings[1:]:
36 36 if not string.startswith(test_prefix):
37 37 return prefix
38 38 prefix = test_prefix
39 39
40 40 return prefix
41 41
42 42 #-------------------------------------------------------------------------------
43 43 # Base class for the line-oriented front ends
44 44 #-------------------------------------------------------------------------------
45 45 class LineFrontEndBase(FrontEndBase):
46 46 """ Concrete implementation of the FrontEndBase class. This is meant
47 47 to be the base class behind all the frontend that are line-oriented,
48 48 rather than block-oriented.
49 49 """
50 50
51 51 # We need to keep the prompt number, to be able to increment
52 52 # it when there is an exception.
53 53 prompt_number = 1
54 54
55 55 # We keep a reference to the last result: it helps testing and
56 56 # programatic control of the frontend.
57 57 last_result = dict(number=0)
58 58
59 # The last prompt displayed. Useful for continuation prompts.
60 last_prompt = ''
61
59 62 # The input buffer being edited
60 63 input_buffer = ''
61 64
62 65 # Set to true for debug output
63 66 debug = False
64 67
65 68 # A banner to print at startup
66 69 banner = None
67 70
68 71 #--------------------------------------------------------------------------
69 72 # FrontEndBase interface
70 73 #--------------------------------------------------------------------------
71 74
72 75 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
73 76 if shell is None:
74 77 shell = Interpreter()
75 78 FrontEndBase.__init__(self, shell=shell, history=history)
76 79
77 80 if banner is not None:
78 81 self.banner = banner
79 82
80 83 def start(self):
81 84 """ Put the frontend in a state where it is ready for user
82 85 interaction.
83 86 """
84 87 if self.banner is not None:
85 88 self.write(self.banner, refresh=False)
86 89
87 90 self.new_prompt(self.input_prompt_template.substitute(number=1))
88 91
89 92
90 93 def complete(self, line):
91 94 """Complete line in engine's user_ns
92 95
93 96 Parameters
94 97 ----------
95 98 line : string
96 99
97 100 Result
98 101 ------
99 102 The replacement for the line and the list of possible completions.
100 103 """
101 104 completions = self.shell.complete(line)
102 105 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
103 106 if completions:
104 107 prefix = common_prefix(completions)
105 108 residual = complete_sep.split(line)[:-1]
106 109 line = line[:-len(residual)] + prefix
107 110 return line, completions
108 111
109 112
110 113 def render_result(self, result):
111 114 """ Frontend-specific rendering of the result of a calculation
112 115 that has been sent to an engine.
113 116 """
114 117 if 'stdout' in result and result['stdout']:
115 118 self.write('\n' + result['stdout'])
116 119 if 'display' in result and result['display']:
117 120 self.write("%s%s\n" % (
118 121 self.output_prompt_template.substitute(
119 122 number=result['number']),
120 123 result['display']['pprint']
121 124 ) )
122 125
123 126
124 127 def render_error(self, failure):
125 128 """ Frontend-specific rendering of error.
126 129 """
127 130 self.write('\n\n'+str(failure)+'\n\n')
128 131 return failure
129 132
130 133
131 134 def is_complete(self, string):
132 135 """ Check if a string forms a complete, executable set of
133 136 commands.
134 137
135 138 For the line-oriented frontend, multi-line code is not executed
136 139 as soon as it is complete: the users has to enter two line
137 140 returns.
138 141 """
139 142 if string in ('', '\n'):
140 143 # Prefiltering, eg through ipython0, may return an empty
141 144 # string although some operations have been accomplished. We
142 145 # thus want to consider an empty string as a complete
143 146 # statement.
144 147 return True
145 148 elif ( len(self.input_buffer.split('\n'))>2
146 149 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
147 150 return False
148 151 else:
149 152 self.capture_output()
150 153 try:
151 154 # Add line returns here, to make sure that the statement is
152 155 # complete (except if '\' was used).
153 156 # This should probably be done in a different place (like
154 157 # maybe 'prefilter_input' method? For now, this works.
155 158 clean_string = string.rstrip('\n')
156 159 if not clean_string.endswith('\\'): clean_string +='\n\n'
157 160 is_complete = codeop.compile_command(clean_string,
158 161 "<string>", "exec")
159 162 self.release_output()
160 163 except Exception, e:
161 164 # XXX: Hack: return True so that the
162 165 # code gets executed and the error captured.
163 166 is_complete = True
164 167 return is_complete
165 168
166 169
167 170 def write(self, string, refresh=True):
168 171 """ Write some characters to the display.
169 172
170 173 Subclass should overide this method.
171 174
172 175 The refresh keyword argument is used in frontends with an
173 176 event loop, to choose whether the write should trigget an UI
174 177 refresh, and thus be syncrhonous, or not.
175 178 """
176 179 print >>sys.__stderr__, string
177 180
178 181
179 182 def execute(self, python_string, raw_string=None):
180 183 """ Stores the raw_string in the history, and sends the
181 184 python string to the interpreter.
182 185 """
183 186 if raw_string is None:
184 187 raw_string = python_string
185 188 # Create a false result, in case there is an exception
186 189 self.last_result = dict(number=self.prompt_number)
187 190
188 191 ## try:
189 192 ## self.history.input_cache[-1] = raw_string.rstrip()
190 193 ## result = self.shell.execute(python_string)
191 194 ## self.last_result = result
192 195 ## self.render_result(result)
193 196 ## except:
194 197 ## self.show_traceback()
195 198 ## finally:
196 199 ## self.after_execute()
197 200
198 201 try:
199 202 try:
200 203 self.history.input_cache[-1] = raw_string.rstrip()
201 204 result = self.shell.execute(python_string)
202 205 self.last_result = result
203 206 self.render_result(result)
204 207 except:
205 208 self.show_traceback()
206 209 finally:
207 210 self.after_execute()
208 211
209 212
210 213 #--------------------------------------------------------------------------
211 214 # LineFrontEndBase interface
212 215 #--------------------------------------------------------------------------
213 216
214 217 def prefilter_input(self, string):
215 218 """ Prefilter the input to turn it in valid python.
216 219 """
217 220 string = string.replace('\r\n', '\n')
218 221 string = string.replace('\t', 4*' ')
219 222 # Clean the trailing whitespace
220 223 string = '\n'.join(l.rstrip() for l in string.split('\n'))
221 224 return string
222 225
223 226
224 227 def after_execute(self):
225 228 """ All the operations required after an execution to put the
226 229 terminal back in a shape where it is usable.
227 230 """
228 231 self.prompt_number += 1
229 232 self.new_prompt(self.input_prompt_template.substitute(
230 233 number=(self.last_result['number'] + 1)))
231 234 # Start a new empty history entry
232 235 self._add_history(None, '')
233 236 self.history_cursor = len(self.history.input_cache) - 1
234 237
235 238
236 239 def complete_current_input(self):
237 240 """ Do code completion on current line.
238 241 """
239 242 if self.debug:
240 243 print >>sys.__stdout__, "complete_current_input",
241 244 line = self.input_buffer
242 245 new_line, completions = self.complete(line)
243 246 if len(completions)>1:
244 247 self.write_completion(completions, new_line=new_line)
245 248 elif not line == new_line:
246 249 self.input_buffer = new_line
247 250 if self.debug:
248 251 print >>sys.__stdout__, 'line', line
249 252 print >>sys.__stdout__, 'new_line', new_line
250 253 print >>sys.__stdout__, completions
251 254
252 255
253 256 def get_line_width(self):
254 257 """ Return the width of the line in characters.
255 258 """
256 259 return 80
257 260
258 261
259 262 def write_completion(self, possibilities, new_line=None):
260 263 """ Write the list of possible completions.
261 264
262 265 new_line is the completed input line that should be displayed
263 266 after the completion are writen. If None, the input_buffer
264 267 before the completion is used.
265 268 """
266 269 if new_line is None:
267 270 new_line = self.input_buffer
268 271
269 272 self.write('\n')
270 273 max_len = len(max(possibilities, key=len)) + 1
271 274
272 275 # Now we check how much symbol we can put on a line...
273 276 chars_per_line = self.get_line_width()
274 277 symbols_per_line = max(1, chars_per_line/max_len)
275 278
276 279 pos = 1
277 280 completion_string = []
278 281 for symbol in possibilities:
279 282 if pos < symbols_per_line:
280 283 completion_string.append(symbol.ljust(max_len))
281 284 pos += 1
282 285 else:
283 286 completion_string.append(symbol.rstrip() + '\n')
284 287 pos = 1
285 288 self.write(''.join(completion_string))
286 289 self.new_prompt(self.input_prompt_template.substitute(
287 290 number=self.last_result['number'] + 1))
288 291 self.input_buffer = new_line
289 292
290 293
291 294 def new_prompt(self, prompt):
292 295 """ Prints a prompt and starts a new editing buffer.
293 296
294 297 Subclasses should use this method to make sure that the
295 298 terminal is put in a state favorable for a new line
296 299 input.
297 300 """
298 301 self.input_buffer = ''
299 302 self.write(prompt)
300 303
301 304
302 305 def continuation_prompt(self):
303 306 """Returns the current continuation prompt.
304 Overridden to generate a continuation prompt matching the length of the
305 current prompt."""
306
307 # FIXME: This is a bad hack.. I need to find a way to use the 'Prompt2'
308 # class in IPython/kernel/prompts.py. Basically, I am trying to get the
309 # length of the current prompt ("In ['number']").
310 return ("."*(5+len(str(self.last_result['number']))) + ':')
307 """
308 return ("."*(len(self.last_prompt)-2) + ': ')
311 309
312 310
313 311 def execute_command(self, command, hidden=False):
314 312 """ Execute a command, not only in the model, but also in the
315 313 view, if any.
316 314 """
317 315 return self.shell.execute(command)
318 316
319 317 #--------------------------------------------------------------------------
320 318 # Private API
321 319 #--------------------------------------------------------------------------
322 320
323 321 def _on_enter(self):
324 322 """ Called when the return key is pressed in a line editing
325 323 buffer.
326 324 """
327 325 current_buffer = self.input_buffer
328 326 # XXX: This string replace is ugly, but there should be no way it
329 327 # fails.
330 328 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
331 329 '', current_buffer).replace('\n' + self.continuation_prompt(),
332 330 '\n')
333 331 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
334 332 if self.is_complete(cleaned_buffer):
335 333 self.execute(cleaned_buffer, raw_string=current_buffer)
336 334 else:
337 335 self.input_buffer += self.continuation_prompt() + \
338 336 self._get_indent_string(prompt_less_buffer[:-1])
339 337 if len(current_buffer.split('\n')) == 2:
340 338 self.input_buffer += '\t'
341 339 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
342 340 self.input_buffer += '\t'
343 341
344 342
345 343 def _get_indent_string(self, string):
346 344 """ Return the string of whitespace that prefixes a line. Used to
347 345 add the right amount of indendation when creating a new line.
348 346 """
349 347 string = string.replace('\t', ' '*4)
350 348 string = string.split('\n')[-1]
351 349 indent_chars = len(string) - len(string.lstrip())
352 350 indent_string = '\t'*(indent_chars // 4) + \
353 351 ' '*(indent_chars % 4)
354 352
355 353 return indent_string
356 354
357 355
@@ -1,507 +1,645 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26 import time
27 27 import sys
28 import string
29
28 30 LINESEP = '\n'
29 31 if sys.platform == 'win32':
30 32 LINESEP = '\n\r'
31 33
32 34 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',
53 61 'string' : 'fore:#7F007F,italic',
54 62 'char' : 'fore:#7F007F,italic',
55 63 'keyword' : 'fore:#00007F,bold',
56 64 'triple' : 'fore:#7F0000',
57 65 'tripledouble' : 'fore:#7F0000',
58 66 'class' : 'fore:#0000FF,bold,underline',
59 67 'def' : 'fore:#007F7F,bold',
60 68 'operator' : 'bold'
61 69 }
62 70
63 71 # new style numbers
64 72 _STDOUT_STYLE = 15
65 73 _STDERR_STYLE = 16
66 74 _TRACE_STYLE = 17
67 75
68 76
69 77 # system colors
70 78 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
71 79
80 # Translation table from ANSI escape sequences to color.
81 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
82 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
83 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
84 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
85 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
86 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
87 '1;34': [12, 'LIGHT BLUE'], '1;35':
88 [13, 'MEDIUM VIOLET RED'],
89 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
90
91 #we define platform specific fonts
92 if wx.Platform == '__WXMSW__':
93 FACES = { 'times': 'Times New Roman',
94 'mono' : 'Courier New',
95 'helv' : 'Arial',
96 'other': 'Comic Sans MS',
97 'size' : 10,
98 'size2': 8,
99 }
100 elif wx.Platform == '__WXMAC__':
101 FACES = { 'times': 'Times New Roman',
102 'mono' : 'Monaco',
103 'helv' : 'Arial',
104 'other': 'Comic Sans MS',
105 'size' : 10,
106 'size2': 8,
107 }
108 else:
109 FACES = { 'times': 'Times',
110 'mono' : 'Courier',
111 'helv' : 'Helvetica',
112 'other': 'new century schoolbook',
113 'size' : 10,
114 'size2': 8,
115 }
116
117
72 118 #-------------------------------------------------------------------------------
73 119 # The console widget class
74 120 #-------------------------------------------------------------------------------
75 121 class ConsoleWidget(editwindow.EditWindow):
76 122 """ Specialized styled text control view for console-like workflow.
77 123
78 124 This widget is mainly interested in dealing with the prompt and
79 125 keeping the cursor inside the editing line.
80 126 """
81 127
82 128 # This is where the title captured from the ANSI escape sequences are
83 129 # stored.
84 130 title = 'Console'
85 131
132 # Last prompt printed
133 last_prompt = ''
134
86 135 # The buffer being edited.
87 136 def _set_input_buffer(self, string):
88 137 self.SetSelection(self.current_prompt_pos, self.GetLength())
89 138 self.ReplaceSelection(string)
90 139 self.GotoPos(self.GetLength())
91 140
92 141 def _get_input_buffer(self):
93 142 """ Returns the text in current edit buffer.
94 143 """
95 144 input_buffer = self.GetTextRange(self.current_prompt_pos,
96 145 self.GetLength())
97 146 input_buffer = input_buffer.replace(LINESEP, '\n')
98 147 return input_buffer
99 148
100 149 input_buffer = property(_get_input_buffer, _set_input_buffer)
101 150
102 151 style = _DEFAULT_STYLE.copy()
103 152
104 153 # Translation table from ANSI escape sequences to color. Override
105 154 # 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'
155 ANSI_STYLES = ANSI_STYLES.copy()
118 156
157 # Font faces
158 faces = FACES.copy()
159
119 160 # Store the last time a refresh was done
120 161 _last_refresh_time = 0
121 162
122 163 #--------------------------------------------------------------------------
123 164 # Public API
124 165 #--------------------------------------------------------------------------
125 166
126 167 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
127 168 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 169 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
129 self._configure_scintilla()
170 self.configure_scintilla()
171 # Track if 'enter' key as ever been processed
172 # This variable will only be reallowed until key goes up
173 self.enter_catched = False
174 self.current_prompt_pos = 0
130 175
131 176 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 177 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
133 178
134 179
135 180 def write(self, text, refresh=True):
136 181 """ Write given text to buffer, while translating the ansi escape
137 182 sequences.
138 183 """
139 184 # XXX: do not put print statements to sys.stdout/sys.stderr in
140 185 # this method, the print statements will call this method, as
141 186 # you will end up with an infinit loop
142 187 title = self.title_pat.split(text)
143 188 if len(title)>1:
144 189 self.title = title[-2]
145 190
146 191 text = self.title_pat.sub('', text)
147 192 segments = self.color_pat.split(text)
148 193 segment = segments.pop(0)
149 194 self.GotoPos(self.GetLength())
150 195 self.StartStyling(self.GetLength(), 0xFF)
151 196 try:
152 197 self.AppendText(segment)
153 198 except UnicodeDecodeError:
154 199 # XXX: Do I really want to skip the exception?
155 200 pass
156 201
157 202 if segments:
158 203 for ansi_tag, text in zip(segments[::2], segments[1::2]):
159 204 self.StartStyling(self.GetLength(), 0xFF)
160 205 try:
161 206 self.AppendText(text)
162 207 except UnicodeDecodeError:
163 208 # XXX: Do I really want to skip the exception?
164 209 pass
165 210
166 211 if ansi_tag not in self.ANSI_STYLES:
167 212 style = 0
168 213 else:
169 214 style = self.ANSI_STYLES[ansi_tag][0]
170 215
171 216 self.SetStyling(len(text), style)
172 217
173 218 self.GotoPos(self.GetLength())
174 219 if refresh:
175 220 current_time = time.time()
176 221 if current_time - self._last_refresh_time > 0.03:
177 222 if sys.platform == 'win32':
178 223 wx.SafeYield()
179 224 else:
180 225 wx.Yield()
181 226 # self.ProcessEvent(wx.PaintEvent())
182 227 self._last_refresh_time = current_time
183 228
184 229
185 230 def new_prompt(self, prompt):
186 231 """ Prints a prompt at start of line, and move the start of the
187 232 current block there.
188 233
189 234 The prompt can be given with ascii escape sequences.
190 235 """
191 236 self.write(prompt, refresh=False)
192 237 # now we update our cursor giving end of prompt
193 238 self.current_prompt_pos = self.GetLength()
194 239 self.current_prompt_line = self.GetCurrentLine()
195 240 self.EnsureCaretVisible()
241 self.last_prompt = prompt
242
196 243
244 def continuation_prompt(self):
245 """Returns the current continuation prompt.
246 """
247 # ASCII-less prompt
248 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
249 return "."*(len(ascii_less)-2) + ': '
250
197 251
198 252 def scroll_to_bottom(self):
199 253 maxrange = self.GetScrollRange(wx.VERTICAL)
200 254 self.ScrollLines(maxrange)
201 255
202 256
203 257 def pop_completion(self, possibilities, offset=0):
204 258 """ Pops up an autocompletion menu. Offset is the offset
205 259 in characters of the position at which the menu should
206 260 appear, relativ to the cursor.
207 261 """
208 262 self.AutoCompSetIgnoreCase(False)
209 263 self.AutoCompSetAutoHide(False)
210 264 self.AutoCompSetMaxHeight(len(possibilities))
211 265 self.AutoCompShow(offset, " ".join(possibilities))
212 266
213 267
214 268 def get_line_width(self):
215 269 """ Return the width of the line in characters.
216 270 """
217 271 return self.GetSize()[0]/self.GetCharWidth()
218 272
219 273
220 def clear_screen(self):
221 """ Empty completely the widget.
222 """
223 self.ClearAll()
224 self.new_prompt(self.input_prompt_template.substitute(
225 number=(self.last_result['number'] + 1)))
226
227
228 274
229 275 #--------------------------------------------------------------------------
230 276 # EditWindow API
231 277 #--------------------------------------------------------------------------
232 278
233 279 def OnUpdateUI(self, event):
234 280 """ Override the OnUpdateUI of the EditWindow class, to prevent
235 281 syntax highlighting both for faster redraw, and for more
236 282 consistent look and feel.
237 283 """
238 284
239 285 #--------------------------------------------------------------------------
240 # Private API
286 # Styling API
241 287 #--------------------------------------------------------------------------
242
243 def _apply_style(self):
244 """ Applies the colors for the different text elements and the
245 carret.
246 """
247 self.SetCaretForeground(self.carret_color)
248 288
249 #self.StyleClearAll()
250 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
251 "fore:#FF0000,back:#0000FF,bold")
252 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
253 "fore:#000000,back:#FF0000,bold")
289 def configure_scintilla(self):
290
291 p = self.style
292
293 #First we define the special background colors
294 if 'trace' in p:
295 _COMPLETE_BUFFER_BG = p['trace']
296 else:
297 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
298
299 if 'stdout' in p:
300 _INPUT_BUFFER_BG = p['stdout']
301 else:
302 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
303
304 if 'stderr' in p:
305 _ERROR_BG = p['stderr']
306 else:
307 _ERROR_BG = '#FFF1F1' # Nice red
254 308
255 for style in self.ANSI_STYLES.values():
256 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
309 # Marker for complete buffer.
310 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
311 background = _COMPLETE_BUFFER_BG)
312
313 # Marker for current input buffer.
314 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
315 background = _INPUT_BUFFER_BG)
316 # Marker for tracebacks.
317 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
318 background = _ERROR_BG)
257 319
258
259 def _configure_scintilla(self):
260 320 self.SetEOLMode(stc.STC_EOL_LF)
261 321
262 322 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
263 323 # the widget
264 324 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
265 325 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
266 326 # Also allow Ctrl Shift "=" for poor non US keyboard users.
267 327 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
268 328 stc.STC_CMD_ZOOMIN)
269 329
270 330 # Keys: we need to clear some of the keys the that don't play
271 331 # well with a console.
272 332 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
273 333 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
274 334 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
275 335 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
276 336
277 337 self.SetEOLMode(stc.STC_EOL_CRLF)
278 338 self.SetWrapMode(stc.STC_WRAP_CHAR)
279 339 self.SetWrapMode(stc.STC_WRAP_WORD)
280 340 self.SetBufferedDraw(True)
281 self.SetUseAntiAliasing(True)
341
342 if 'antialiasing' in p:
343 self.SetUseAntiAliasing(p['antialiasing'])
344 else:
345 self.SetUseAntiAliasing(True)
346
282 347 self.SetLayoutCache(stc.STC_CACHE_PAGE)
283 348 self.SetUndoCollection(False)
284 349 self.SetUseTabs(True)
285 350 self.SetIndent(4)
286 351 self.SetTabWidth(4)
287 352
288 353 # we don't want scintilla's autocompletion to choose
289 354 # automaticaly out of a single choice list, as we pop it up
290 355 # automaticaly
291 356 self.AutoCompSetChooseSingle(False)
292 357 self.AutoCompSetMaxHeight(10)
293 358 # XXX: this doesn't seem to have an effect.
294 359 self.AutoCompSetFillUps('\n')
295 360
296 361 self.SetMargins(3, 3) #text is moved away from border with 3px
297 362 # Suppressing Scintilla margins
298 363 self.SetMarginWidth(0, 0)
299 364 self.SetMarginWidth(1, 0)
300 365 self.SetMarginWidth(2, 0)
301 366
302 self._apply_style()
303
304 367 # Xterm escape sequences
305 368 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
306 369 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
307 370
308 #self.SetEdgeMode(stc.STC_EDGE_LINE)
309 #self.SetEdgeColumn(80)
310
311 371 # styles
312 p = self.style
313 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
372
373 if 'carret_color' in p:
374 self.SetCaretForeground(p['carret_color'])
375 else:
376 self.SetCaretForeground('BLACK')
377
378 if 'background_color' in p:
379 background_color = p['background_color']
380 else:
381 background_color = 'WHITE'
382
383 if 'default' in p:
384 if 'back' not in p['default']:
385 p['default'] += ',back:%s' % background_color
386 if 'size' not in p['default']:
387 p['default'] += ',size:%s' % self.faces['size']
388 if 'face' not in p['default']:
389 p['default'] += ',face:%s' % self.faces['mono']
390
391 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
392 else:
393 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
394 "fore:%s,back:%s,size:%d,face:%s"
395 % (self.ANSI_STYLES['0;30'][1],
396 background_color,
397 self.faces['size'], self.faces['mono']))
398
399 #all styles = default one
314 400 self.StyleClearAll()
315 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
316 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
317 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
318
319 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
320 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
321 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
322 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
323 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
324 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
325 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
326 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
327 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
328 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
329 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
330 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
331 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
332 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
401
402 # XXX: two lines below are usefull if not using the lexer
403 #for style in self.ANSI_STYLES.values():
404 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
405
406 #prompt definition
407 if 'prompt_in1' in p:
408 self.prompt_in1 = p['prompt_in1']
409 else:
410 self.prompt_in1 = \
411 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
333 412
413 if 'prompt_out' in p:
414 self.prompt_out = p['prompt_out']
415 else:
416 self.prompt_out = \
417 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
418
419 self.output_prompt_template = string.Template(self.prompt_out)
420 self.input_prompt_template = string.Template(self.prompt_in1)
421
422 if 'stdout' in p:
423 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
424 if 'stderr' in p:
425 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
426 if 'trace' in p:
427 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
428 if 'bracegood' in p:
429 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
430 if 'bracebad' in p:
431 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
432 if 'comment' in p:
433 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
434 if 'number' in p:
435 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
436 if 'string' in p:
437 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
438 if 'char' in p:
439 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
440 if 'keyword' in p:
441 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
442 if 'keyword' in p:
443 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
444 if 'triple' in p:
445 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
446 if 'tripledouble' in p:
447 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
448 if 'class' in p:
449 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
450 if 'def' in p:
451 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
452 if 'operator' in p:
453 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
454 if 'comment' in p:
455 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
456
457 if 'edge_column' in p:
458 edge_column = p['edge_column']
459 if edge_column is not None and edge_column > 0:
460 #we add a vertical line to console widget
461 self.SetEdgeMode(stc.STC_EDGE_LINE)
462 self.SetEdgeColumn(88)
463
464
465 #--------------------------------------------------------------------------
466 # Private API
467 #--------------------------------------------------------------------------
468
334 469 def _on_key_down(self, event, skip=True):
335 470 """ Key press callback used for correcting behavior for
336 471 console-like interfaces: the cursor is constraint to be after
337 472 the last prompt.
338 473
339 474 Return True if event as been catched.
340 475 """
341 476 catched = True
342 477 # Intercept some specific keys.
343 478 if event.KeyCode == ord('L') and event.ControlDown() :
344 479 self.scroll_to_bottom()
345 480 elif event.KeyCode == ord('K') and event.ControlDown() :
346 481 self.input_buffer = ''
347 482 elif event.KeyCode == ord('A') and event.ControlDown() :
348 483 self.GotoPos(self.GetLength())
349 484 self.SetSelectionStart(self.current_prompt_pos)
350 485 self.SetSelectionEnd(self.GetCurrentPos())
351 486 catched = True
352 487 elif event.KeyCode == ord('E') and event.ControlDown() :
353 488 self.GotoPos(self.GetLength())
354 489 catched = True
355 490 elif event.KeyCode == wx.WXK_PAGEUP:
356 491 self.ScrollPages(-1)
357 492 elif event.KeyCode == wx.WXK_PAGEDOWN:
358 493 self.ScrollPages(1)
359 494 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
360 495 self.ScrollLines(-1)
361 496 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
362 497 self.ScrollLines(1)
363 498 else:
364 499 catched = False
365 500
366 501 if self.AutoCompActive():
367 502 event.Skip()
368 503 else:
369 504 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
370 505 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
371 506 catched = True
372 self.CallTipCancel()
373 self.write('\n', refresh=False)
374 # Under windows scintilla seems to be doing funny stuff to the
375 # line returns here, but the getter for input_buffer filters
376 # this out.
377 if sys.platform == 'win32':
378 self.input_buffer = self.input_buffer
379 self._on_enter()
507 if not self.enter_catched:
508 self.CallTipCancel()
509 self.write('\n', refresh=False)
510 # Under windows scintilla seems to be doing funny
511 # stuff to the line returns here, but the getter for
512 # input_buffer filters this out.
513 if sys.platform == 'win32':
514 self.input_buffer = self.input_buffer
515 self._on_enter()
516 self.enter_catched = True
380 517
381 518 elif event.KeyCode == wx.WXK_HOME:
382 519 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
383 520 self.GotoPos(self.current_prompt_pos)
384 521 catched = True
385 522
386 523 elif event.Modifiers == wx.MOD_SHIFT:
387 524 # FIXME: This behavior is not ideal: if the selection
388 525 # is already started, it will jump.
389 526 self.SetSelectionStart(self.current_prompt_pos)
390 527 self.SetSelectionEnd(self.GetCurrentPos())
391 528 catched = True
392 529
393 530 elif event.KeyCode == wx.WXK_UP:
394 531 if self.GetCurrentLine() > self.current_prompt_line:
395 532 if self.GetCurrentLine() == self.current_prompt_line + 1 \
396 533 and self.GetColumn(self.GetCurrentPos()) < \
397 534 self.GetColumn(self.current_prompt_pos):
398 535 self.GotoPos(self.current_prompt_pos)
399 536 else:
400 537 event.Skip()
401 538 catched = True
402 539
403 540 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
404 541 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
405 542 event.Skip()
406 543 catched = True
407 544
408 545 elif event.KeyCode == wx.WXK_RIGHT:
409 546 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
410 547 event.Skip()
411 548 catched = True
412 549
413 550
414 551 elif event.KeyCode == wx.WXK_DELETE:
415 552 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
416 553 event.Skip()
417 554 catched = True
418 555
419 556 if skip and not catched:
420 557 # Put the cursor back in the edit region
421 558 if not self._keep_cursor_in_buffer():
422 559 if not (self.GetCurrentPos() == self.GetLength()
423 560 and event.KeyCode == wx.WXK_DELETE):
424 561 event.Skip()
425 562 catched = True
426 563
427 564 return catched
428 565
429 566
430 567 def _on_key_up(self, event, skip=True):
431 568 """ If cursor is outside the editing region, put it back.
432 569 """
433 570 if skip:
434 571 event.Skip()
435 572 self._keep_cursor_in_buffer()
436 573
437 574
438 575 def _keep_cursor_in_buffer(self, pos=None):
439 576 """ Checks if the cursor is where it is allowed to be. If not,
440 577 put it back.
441 578
442 579 Returns
443 580 -------
444 581 cursor_moved: Boolean
445 582 whether or not the cursor was moved by this routine.
446 583
447 584 Notes
448 585 ------
449 586 WARNING: This does proper checks only for horizontal
450 587 movements.
451 588 """
452 589 if pos is None:
453 590 current_pos = self.GetCurrentPos()
454 591 else:
455 592 current_pos = pos
456 593 if current_pos < self.current_prompt_pos:
457 594 self.GotoPos(self.current_prompt_pos)
458 595 return True
459 596 line_num = self.LineFromPosition(current_pos)
460 597 if not current_pos > self.GetLength():
461 598 line_pos = self.GetColumn(current_pos)
462 599 else:
463 600 line_pos = self.GetColumn(self.GetLength())
464 601 line = self.GetLine(line_num)
465 602 # Jump the continuation prompt
466 603 continuation_prompt = self.continuation_prompt()
467 604 if ( line.startswith(continuation_prompt)
468 605 and line_pos < len(continuation_prompt)+1):
469 606 if line_pos < 2:
470 607 # We are at the beginning of the line, trying to move
471 608 # forward: jump forward.
472 609 self.GotoPos(current_pos + 1 +
473 610 len(continuation_prompt) - line_pos)
474 611 else:
475 612 # Jump back up
476 613 self.GotoPos(self.GetLineEndPosition(line_num-1))
477 614 return True
478 615 elif ( current_pos > self.GetLineEndPosition(line_num)
479 616 and not current_pos == self.GetLength()):
480 617 # Jump to next line
481 618 self.GotoPos(current_pos + 1 +
482 619 len(continuation_prompt))
483 620 return True
484 return False
485 621
622 self.enter_catched = False #we re-allow enter event processing
623 return False
486 624
487 625
488 626 if __name__ == '__main__':
489 627 # Some simple code to test the console widget.
490 628 class MainWindow(wx.Frame):
491 629 def __init__(self, parent, id, title):
492 wx.Frame.__init__(self, parent, id, title, size=(300,250))
630 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
493 631 self._sizer = wx.BoxSizer(wx.VERTICAL)
494 632 self.console_widget = ConsoleWidget(self)
495 633 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
496 634 self.SetSizer(self._sizer)
497 635 self.SetAutoLayout(1)
498 636 self.Show(True)
499 637
500 638 app = wx.PySimpleApp()
501 639 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
502 640 w.SetSize((780, 460))
503 641 w.Show()
504 642
505 643 app.MainLoop()
506 644
507 645
@@ -1,561 +1,551 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 This class inherits from ConsoleWidget, that provides a console-like
9 9 widget to provide a text-rendering widget suitable for a terminal.
10 10 """
11 11
12 12 __docformat__ = "restructuredtext en"
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-------------------------------------------------------------------------------
20 20
21 21 #-------------------------------------------------------------------------------
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24
25 25 # Major library imports
26 26 import re
27 27 import __builtin__
28 28 import sys
29 29 from threading import Lock
30 import string
31 30
32 31 import wx
33 32 from wx import stc
34 33
35 34 # Ipython-specific imports.
36 35 from IPython.frontend._process import PipedProcess
37 from console_widget import ConsoleWidget
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 _ERROR_MARKER, _INPUT_MARKER
38 38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39 39
40 40 #-------------------------------------------------------------------------------
41 # Constants
42 #-------------------------------------------------------------------------------
43
44 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
45 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
46 _ERROR_BG = '#FFF1F1' # Nice red
47
48 _COMPLETE_BUFFER_MARKER = 31
49 _ERROR_MARKER = 30
50 _INPUT_MARKER = 29
51
52 prompt_in1 = \
53 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
54
55 prompt_out = \
56 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
57
58 #-------------------------------------------------------------------------------
59 41 # Classes to implement the Wx frontend
60 42 #-------------------------------------------------------------------------------
61 43 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 44 """Classes to provide a Wx frontend to the
63 45 IPython.kernel.core.interpreter.
64 46
65 47 This class inherits from ConsoleWidget, that provides a console-like
66 48 widget to provide a text-rendering widget suitable for a terminal.
67 49 """
68
69 output_prompt_template = string.Template(prompt_out)
70
71 input_prompt_template = string.Template(prompt_in1)
72
50
73 51 # Print debug info on what is happening to the console.
74 52 debug = False
75 53
76 54 # The title of the terminal, as captured through the ANSI escape
77 55 # sequences.
78 56 def _set_title(self, title):
79 57 return self.Parent.SetTitle(title)
80 58
81 59 def _get_title(self):
82 60 return self.Parent.GetTitle()
83 61
84 62 title = property(_get_title, _set_title)
85 63
86 64
87 65 # The buffer being edited.
88 66 # We are duplicating the definition here because of multiple
89 67 # inheritence
90 68 def _set_input_buffer(self, string):
91 69 ConsoleWidget._set_input_buffer(self, string)
92 70 self._colorize_input_buffer()
93 71
94 72 def _get_input_buffer(self):
95 73 """ Returns the text in current edit buffer.
96 74 """
97 75 return ConsoleWidget._get_input_buffer(self)
98 76
99 77 input_buffer = property(_get_input_buffer, _set_input_buffer)
100 78
101 79
102 80 #--------------------------------------------------------------------------
103 81 # Private Attributes
104 82 #--------------------------------------------------------------------------
105 83
106 84 # A flag governing the behavior of the input. Can be:
107 85 #
108 86 # 'readline' for readline-like behavior with a prompt
109 87 # and an edit buffer.
110 88 # 'raw_input' similar to readline, but triggered by a raw-input
111 89 # call. Can be used by subclasses to act differently.
112 90 # 'subprocess' for sending the raw input directly to a
113 91 # subprocess.
114 92 # 'buffering' for buffering of the input, that will be used
115 93 # when the input state switches back to another state.
116 94 _input_state = 'readline'
117 95
118 96 # Attribute to store reference to the pipes of a subprocess, if we
119 97 # are running any.
120 98 _running_process = False
121 99
122 100 # A queue for writing fast streams to the screen without flooding the
123 101 # event loop
124 102 _out_buffer = []
125 103
126 104 # A lock to lock the _out_buffer to make sure we don't empty it
127 105 # while it is being swapped
128 106 _out_buffer_lock = Lock()
129 107
130 108 # The different line markers used to higlight the prompts.
131 109 _markers = dict()
132 110
133 111 #--------------------------------------------------------------------------
134 112 # Public API
135 113 #--------------------------------------------------------------------------
136 114
137 115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 116 size=wx.DefaultSize,
139 117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 styledef=None,
140 119 *args, **kwds):
141 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.
142 127 """
128 if styledef is not None:
129 self.style = styledef
143 130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
144 131 PrefilterFrontEnd.__init__(self, **kwds)
145 132
146 133 # Stick in our own raw_input:
147 134 self.ipython0.raw_input = self.raw_input
148 135
149 # Marker for complete buffer.
150 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
151 background=_COMPLETE_BUFFER_BG)
152 # Marker for current input buffer.
153 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
154 background=_INPUT_BUFFER_BG)
155 # Marker for tracebacks.
156 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
157 background=_ERROR_BG)
158
159 136 # A time for flushing the write buffer
160 137 BUFFER_FLUSH_TIMER_ID = 100
161 138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
162 139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
163 140
164 141 if 'debug' in kwds:
165 142 self.debug = kwds['debug']
166 143 kwds.pop('debug')
167 144
168 145 # Inject self in namespace, for debug
169 146 if self.debug:
170 147 self.shell.user_ns['self'] = self
171 148 # Inject our own raw_input in namespace
172 149 self.shell.user_ns['raw_input'] = self.raw_input
173
174
150
175 151 def raw_input(self, prompt=''):
176 152 """ A replacement from python's raw_input.
177 153 """
178 154 self.new_prompt(prompt)
179 155 self._input_state = 'raw_input'
180 156 if hasattr(self, '_cursor'):
181 157 del self._cursor
182 158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
183 159 self.__old_on_enter = self._on_enter
184 160 event_loop = wx.EventLoop()
185 161 def my_on_enter():
186 162 event_loop.Exit()
187 163 self._on_enter = my_on_enter
188 164 # XXX: Running a separate event_loop. Ugly.
189 165 event_loop.Run()
190 166 self._on_enter = self.__old_on_enter
191 167 self._input_state = 'buffering'
192 168 self._cursor = wx.BusyCursor()
193 169 return self.input_buffer.rstrip('\n')
194 170
195 171
196 172 def system_call(self, command_string):
197 173 self._input_state = 'subprocess'
198 174 event_loop = wx.EventLoop()
199 175 def _end_system_call():
200 176 self._input_state = 'buffering'
201 177 self._running_process = False
202 178 event_loop.Exit()
203 179
204 180 self._running_process = PipedProcess(command_string,
205 181 out_callback=self.buffered_write,
206 182 end_callback = _end_system_call)
207 183 self._running_process.start()
208 184 # XXX: Running a separate event_loop. Ugly.
209 185 event_loop.Run()
210 186 # Be sure to flush the buffer.
211 187 self._buffer_flush(event=None)
212 188
213 189
214 190 def do_calltip(self):
215 191 """ Analyse current and displays useful calltip for it.
216 192 """
217 193 if self.debug:
218 194 print >>sys.__stdout__, "do_calltip"
219 195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
220 196 symbol = self.input_buffer
221 197 symbol_string = separators.split(symbol)[-1]
222 198 base_symbol_string = symbol_string.split('.')[0]
223 199 if base_symbol_string in self.shell.user_ns:
224 200 symbol = self.shell.user_ns[base_symbol_string]
225 201 elif base_symbol_string in self.shell.user_global_ns:
226 202 symbol = self.shell.user_global_ns[base_symbol_string]
227 203 elif base_symbol_string in __builtin__.__dict__:
228 204 symbol = __builtin__.__dict__[base_symbol_string]
229 205 else:
230 206 return False
231 207 try:
232 208 for name in symbol_string.split('.')[1:] + ['__doc__']:
233 209 symbol = getattr(symbol, name)
234 210 self.AutoCompCancel()
235 211 # Check that the symbol can indeed be converted to a string:
236 212 symbol += ''
237 213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
238 214 except:
239 215 # The retrieve symbol couldn't be converted to a string
240 216 pass
241 217
242 218
243 219 def _popup_completion(self, create=False):
244 220 """ Updates the popup completion menu if it exists. If create is
245 221 true, open the menu.
246 222 """
247 223 if self.debug:
248 224 print >>sys.__stdout__, "_popup_completion"
249 225 line = self.input_buffer
250 226 if (self.AutoCompActive() and line and not line[-1] == '.') \
251 227 or create==True:
252 228 suggestion, completions = self.complete(line)
253 229 if completions:
254 230 offset = len(self._get_completion_text(line))
255 231 self.pop_completion(completions, offset=offset)
256 232 if self.debug:
257 233 print >>sys.__stdout__, completions
258 234
259 235
260 236 def buffered_write(self, text):
261 237 """ A write method for streams, that caches the stream in order
262 238 to avoid flooding the event loop.
263 239
264 240 This can be called outside of the main loop, in separate
265 241 threads.
266 242 """
267 243 self._out_buffer_lock.acquire()
268 244 self._out_buffer.append(text)
269 245 self._out_buffer_lock.release()
270 246 if not self._buffer_flush_timer.IsRunning():
271 247 wx.CallAfter(self._buffer_flush_timer.Start,
272 248 milliseconds=100, oneShot=True)
273 249
274 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
275 259 #--------------------------------------------------------------------------
276 260 # LineFrontEnd interface
277 261 #--------------------------------------------------------------------------
278 262
279 263 def execute(self, python_string, raw_string=None):
280 264 self._input_state = 'buffering'
281 265 self.CallTipCancel()
282 266 self._cursor = wx.BusyCursor()
283 267 if raw_string is None:
284 268 raw_string = python_string
285 269 end_line = self.current_prompt_line \
286 270 + max(1, len(raw_string.split('\n'))-1)
287 271 for i in range(self.current_prompt_line, end_line):
288 272 if i in self._markers:
289 273 self.MarkerDeleteHandle(self._markers[i])
290 274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
291 275 # Use a callafter to update the display robustly under windows
292 276 def callback():
293 277 self.GotoPos(self.GetLength())
294 278 PrefilterFrontEnd.execute(self, python_string,
295 279 raw_string=raw_string)
296 280 wx.CallAfter(callback)
297 281
298 282
299 283 def execute_command(self, command, hidden=False):
300 284 """ Execute a command, not only in the model, but also in the
301 285 view.
302 286 """
303 287 if hidden:
304 288 return self.shell.execute(command)
305 289 else:
306 290 # XXX: we are not storing the input buffer previous to the
307 291 # execution, as this forces us to run the execution
308 292 # input_buffer a yield, which is not good.
309 293 ##current_buffer = self.shell.control.input_buffer
310 294 command = command.rstrip()
311 295 if len(command.split('\n')) > 1:
312 296 # The input command is several lines long, we need to
313 297 # force the execution to happen
314 298 command += '\n'
315 299 cleaned_command = self.prefilter_input(command)
316 300 self.input_buffer = command
317 301 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
318 302 # recursive yields.
319 303 self.ProcessEvent(wx.PaintEvent())
320 304 self.write('\n')
321 305 if not self.is_complete(cleaned_command + '\n'):
322 306 self._colorize_input_buffer()
323 307 self.render_error('Incomplete or invalid input')
324 308 self.new_prompt(self.input_prompt_template.substitute(
325 309 number=(self.last_result['number'] + 1)))
326 310 return False
327 311 self._on_enter()
328 312 return True
329 313
330 314
331 315 def save_output_hooks(self):
332 316 self.__old_raw_input = __builtin__.raw_input
333 317 PrefilterFrontEnd.save_output_hooks(self)
334 318
335 319 def capture_output(self):
336 320 self.SetLexer(stc.STC_LEX_NULL)
337 321 PrefilterFrontEnd.capture_output(self)
338 322 __builtin__.raw_input = self.raw_input
339 323
340 324
341 325 def release_output(self):
342 326 __builtin__.raw_input = self.__old_raw_input
343 327 PrefilterFrontEnd.release_output(self)
344 328 self.SetLexer(stc.STC_LEX_PYTHON)
345 329
346 330
347 331 def after_execute(self):
348 332 PrefilterFrontEnd.after_execute(self)
349 333 # Clear the wait cursor
350 334 if hasattr(self, '_cursor'):
351 335 del self._cursor
352 336 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
353 337
354 338
355 339 def show_traceback(self):
356 340 start_line = self.GetCurrentLine()
357 341 PrefilterFrontEnd.show_traceback(self)
358 342 self.ProcessEvent(wx.PaintEvent())
359 343 #wx.Yield()
360 344 for i in range(start_line, self.GetCurrentLine()):
361 345 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
362 346
363 347
364 348 #--------------------------------------------------------------------------
365 349 # FrontEndBase interface
366 350 #--------------------------------------------------------------------------
367 351
368 352 def render_error(self, e):
369 353 start_line = self.GetCurrentLine()
370 354 self.write('\n' + e + '\n')
371 355 for i in range(start_line, self.GetCurrentLine()):
372 356 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
373 357
374 358
375 359 #--------------------------------------------------------------------------
376 360 # ConsoleWidget interface
377 361 #--------------------------------------------------------------------------
378 362
379 363 def new_prompt(self, prompt):
380 364 """ Display a new prompt, and start a new input buffer.
381 365 """
382 366 self._input_state = 'readline'
383 367 ConsoleWidget.new_prompt(self, prompt)
384 368 i = self.current_prompt_line
385 369 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
386 370
387 371
372 def continuation_prompt(self, *args, **kwargs):
373 # Avoid multiple inheritence, be explicit about which
374 # parent method class gets called
375 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
376
377
388 378 def write(self, *args, **kwargs):
389 379 # Avoid multiple inheritence, be explicit about which
390 380 # parent method class gets called
391 ConsoleWidget.write(self, *args, **kwargs)
381 return ConsoleWidget.write(self, *args, **kwargs)
392 382
393 383
394 384 def _on_key_down(self, event, skip=True):
395 385 """ Capture the character events, let the parent
396 386 widget handle them, and put our logic afterward.
397 387 """
398 388 # FIXME: This method needs to be broken down in smaller ones.
399 389 current_line_number = self.GetCurrentLine()
400 390 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
401 391 # Capture Control-C
402 392 if self._input_state == 'subprocess':
403 393 if self.debug:
404 394 print >>sys.__stderr__, 'Killing running process'
405 395 if hasattr(self._running_process, 'process'):
406 396 self._running_process.process.kill()
407 397 elif self._input_state == 'buffering':
408 398 if self.debug:
409 399 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
410 400 raise KeyboardInterrupt
411 401 # XXX: We need to make really sure we
412 402 # get back to a prompt.
413 403 elif self._input_state == 'subprocess' and (
414 404 ( event.KeyCode<256 and
415 405 not event.ControlDown() )
416 406 or
417 407 ( event.KeyCode in (ord('d'), ord('D')) and
418 408 event.ControlDown())):
419 409 # We are running a process, we redirect keys.
420 410 ConsoleWidget._on_key_down(self, event, skip=skip)
421 411 char = chr(event.KeyCode)
422 412 # Deal with some inconsistency in wx keycodes:
423 413 if char == '\r':
424 414 char = '\n'
425 415 elif not event.ShiftDown():
426 416 char = char.lower()
427 417 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
428 418 char = '\04'
429 419 self._running_process.process.stdin.write(char)
430 420 self._running_process.process.stdin.flush()
431 421 elif event.KeyCode in (ord('('), 57, 53):
432 422 # Calltips
433 423 event.Skip()
434 424 self.do_calltip()
435 425 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
436 426 event.Skip()
437 427 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
438 428 wx.CallAfter(self._popup_completion, create=True)
439 429 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
440 430 wx.WXK_RIGHT, wx.WXK_ESCAPE):
441 431 wx.CallAfter(self._popup_completion)
442 432 else:
443 433 # Up history
444 434 if event.KeyCode == wx.WXK_UP and (
445 435 ( current_line_number == self.current_prompt_line and
446 436 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
447 437 or event.ControlDown() ):
448 438 new_buffer = self.get_history_previous(
449 439 self.input_buffer)
450 440 if new_buffer is not None:
451 441 self.input_buffer = new_buffer
452 442 if self.GetCurrentLine() > self.current_prompt_line:
453 443 # Go to first line, for seemless history up.
454 444 self.GotoPos(self.current_prompt_pos)
455 445 # Down history
456 446 elif event.KeyCode == wx.WXK_DOWN and (
457 447 ( current_line_number == self.LineCount -1 and
458 448 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
459 449 or event.ControlDown() ):
460 450 new_buffer = self.get_history_next()
461 451 if new_buffer is not None:
462 452 self.input_buffer = new_buffer
463 453 # Tab-completion
464 454 elif event.KeyCode == ord('\t'):
465 455 current_line, current_line_number = self.CurLine
466 456 if not re.match(r'^\s*$', current_line):
467 457 self.complete_current_input()
468 458 if self.AutoCompActive():
469 459 wx.CallAfter(self._popup_completion, create=True)
470 460 else:
471 461 event.Skip()
472 462 else:
473 463 ConsoleWidget._on_key_down(self, event, skip=skip)
474 464
475 465
476 466 def _on_key_up(self, event, skip=True):
477 467 """ Called when any key is released.
478 468 """
479 469 if event.KeyCode in (59, ord('.')):
480 470 # Intercepting '.'
481 471 event.Skip()
482 472 wx.CallAfter(self._popup_completion, create=True)
483 473 else:
484 474 ConsoleWidget._on_key_up(self, event, skip=skip)
485 475 if (self.input_buffer.split('\n')[-1] == self.continuation_prompt()
486 476 and self._input_state == 'readline'):
487 477 # Make sure the continuation_prompt is followed by a whitespace
488 478 position = self.GetCurrentPos()
489 479 self.input_buffer += ' '
490 480 self.GotoPos(position)
491 481
492 482
493 483 def _on_enter(self):
494 484 """ Called on return key down, in readline input_state.
495 485 """
496 486 if self.debug:
497 487 print >>sys.__stdout__, repr(self.input_buffer)
498 488 PrefilterFrontEnd._on_enter(self)
499 489
500 490
501 491 #--------------------------------------------------------------------------
502 492 # EditWindow API
503 493 #--------------------------------------------------------------------------
504 494
505 495 def OnUpdateUI(self, event):
506 496 """ Override the OnUpdateUI of the EditWindow class, to prevent
507 497 syntax highlighting both for faster redraw, and for more
508 498 consistent look and feel.
509 499 """
510 500 if not self._input_state == 'readline':
511 501 ConsoleWidget.OnUpdateUI(self, event)
512 502
513 503 #--------------------------------------------------------------------------
514 504 # Private API
515 505 #--------------------------------------------------------------------------
516 506
517 507 def _buffer_flush(self, event):
518 508 """ Called by the timer to flush the write buffer.
519 509
520 510 This is always called in the mainloop, by the wx timer.
521 511 """
522 512 self._out_buffer_lock.acquire()
523 513 _out_buffer = self._out_buffer
524 514 self._out_buffer = []
525 515 self._out_buffer_lock.release()
526 516 self.write(''.join(_out_buffer), refresh=False)
527 517
528 518
529 519 def _colorize_input_buffer(self):
530 520 """ Keep the input buffer lines at a bright color.
531 521 """
532 522 if not self._input_state in ('readline', 'raw_input'):
533 523 return
534 524 end_line = self.GetCurrentLine()
535 525 if not sys.platform == 'win32':
536 526 end_line += 1
537 527 for i in range(self.current_prompt_line, end_line):
538 528 if i in self._markers:
539 529 self.MarkerDeleteHandle(self._markers[i])
540 530 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
541 531
542 532
543 533 if __name__ == '__main__':
544 534 class MainWindow(wx.Frame):
545 535 def __init__(self, parent, id, title):
546 536 wx.Frame.__init__(self, parent, id, title, size=(300,250))
547 537 self._sizer = wx.BoxSizer(wx.VERTICAL)
548 538 self.shell = WxController(self)
549 539 self._sizer.Add(self.shell, 1, wx.EXPAND)
550 540 self.SetSizer(self._sizer)
551 541 self.SetAutoLayout(1)
552 542 self.Show(True)
553 543
554 544 app = wx.PySimpleApp()
555 545 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
556 546 frame.shell.SetFocus()
557 547 frame.SetSize((680, 460))
558 548 self = frame.shell
559 549
560 550 app.MainLoop()
561 551
General Comments 0
You need to be logged in to leave comments. Login now