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