##// END OF EJS Templates
Isolate the displayhook created by ipython0. This fixes a test not...
Gael Varoquaux -
Show More
@@ -1,192 +1,203 b''
1 """
1 """
2 Frontend class that uses IPython0 to prefilter the inputs.
2 Frontend class that uses IPython0 to prefilter the inputs.
3
3
4 Using the IPython0 mechanism gives us access to the magics.
4 Using the IPython0 mechanism gives us access to the magics.
5
5
6 This is a transitory class, used here to do the transition between
6 This is a transitory class, used here to do the transition between
7 ipython0 and ipython1. This class is meant to be short-lived as more
7 ipython0 and ipython1. This class is meant to be short-lived as more
8 functionnality is abstracted out of ipython0 in reusable functions and
8 functionnality is abstracted out of ipython0 in reusable functions and
9 is added on the interpreter. This class can be a used to guide this
9 is added on the interpreter. This class can be a used to guide this
10 refactoring.
10 refactoring.
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 import sys
24 import sys
25
25
26 from linefrontendbase import LineFrontEndBase, common_prefix
26 from linefrontendbase import LineFrontEndBase, common_prefix
27
27
28 from IPython.ipmaker import make_IPython
28 from IPython.ipmaker import make_IPython
29 from IPython.ipapi import IPApi
29 from IPython.ipapi import IPApi
30 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
30 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
31
31
32 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
32 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
33
33
34 from IPython.genutils import Term
34 from IPython.genutils import Term
35 import pydoc
35 import pydoc
36 import os
36 import os
37
37
38
38
39 def mk_system_call(system_call_function, command):
39 def mk_system_call(system_call_function, command):
40 """ given a os.system replacement, and a leading string command,
40 """ given a os.system replacement, and a leading string command,
41 returns a function that will execute the command with the given
41 returns a function that will execute the command with the given
42 argument string.
42 argument string.
43 """
43 """
44 def my_system_call(args):
44 def my_system_call(args):
45 system_call_function("%s %s" % (command, args))
45 system_call_function("%s %s" % (command, args))
46 return my_system_call
46 return my_system_call
47
47
48 #-------------------------------------------------------------------------------
48 #-------------------------------------------------------------------------------
49 # Frontend class using ipython0 to do the prefiltering.
49 # Frontend class using ipython0 to do the prefiltering.
50 #-------------------------------------------------------------------------------
50 #-------------------------------------------------------------------------------
51 class PrefilterFrontEnd(LineFrontEndBase):
51 class PrefilterFrontEnd(LineFrontEndBase):
52 """ Class that uses ipython0 to do prefilter the input, do the
52 """ Class that uses ipython0 to do prefilter the input, do the
53 completion and the magics.
53 completion and the magics.
54
54
55 The core trick is to use an ipython0 instance to prefilter the
55 The core trick is to use an ipython0 instance to prefilter the
56 input, and share the namespace between the interpreter instance used
56 input, and share the namespace between the interpreter instance used
57 to execute the statements and the ipython0 used for code
57 to execute the statements and the ipython0 used for code
58 completion...
58 completion...
59 """
59 """
60
60
61 def __init__(self, *args, **kwargs):
61 def __init__(self, *args, **kwargs):
62 LineFrontEndBase.__init__(self, *args, **kwargs)
62 LineFrontEndBase.__init__(self, *args, **kwargs)
63 self.save_output_hooks()
63 # Instanciate an IPython0 interpreter to be able to use the
64 # Instanciate an IPython0 interpreter to be able to use the
64 # prefiltering.
65 # prefiltering.
65 self.ipython0 = make_IPython()
66 self.ipython0 = make_IPython()
66 # Set the pager:
67 # Set the pager:
67 self.ipython0.set_hook('show_in_pager',
68 self.ipython0.set_hook('show_in_pager',
68 lambda s, string: self.write("\n"+string))
69 lambda s, string: self.write("\n"+string))
69 self.ipython0.write = self.write
70 self.ipython0.write = self.write
70 self._ip = _ip = IPApi(self.ipython0)
71 self._ip = _ip = IPApi(self.ipython0)
71 # XXX: Hack: mix the two namespaces
72 # XXX: Hack: mix the two namespaces
72 self.shell.user_ns = self.ipython0.user_ns
73 self.shell.user_ns = self.ipython0.user_ns
73 self.shell.user_global_ns = self.ipython0.user_global_ns
74 self.shell.user_global_ns = self.ipython0.user_global_ns
74 # Make sure the raw system call doesn't get called, as we don't
75 # Make sure the raw system call doesn't get called, as we don't
75 # have a stdin accessible.
76 # have a stdin accessible.
76 self._ip.system = self.system_call
77 self._ip.system = self.system_call
77 # XXX: Muck around with magics so that they work better
78 # XXX: Muck around with magics so that they work better
78 # in our environment
79 # in our environment
79 self.ipython0.magic_ls = mk_system_call(self.system_call,
80 self.ipython0.magic_ls = mk_system_call(self.system_call,
80 'ls -CF')
81 'ls -CF')
82 # And now clean up the mess created by ipython0
83 self.release_output()
81 self.shell.output_trap = RedirectorOutputTrap(
84 self.shell.output_trap = RedirectorOutputTrap(
82 out_callback=self.write,
85 out_callback=self.write,
83 err_callback=self.write,
86 err_callback=self.write,
84 )
87 )
85 self.shell.traceback_trap = SyncTracebackTrap(
88 self.shell.traceback_trap = SyncTracebackTrap(
86 formatters=self.shell.traceback_trap.formatters,
89 formatters=self.shell.traceback_trap.formatters,
87 )
90 )
88 # Capture and release the outputs, to make sure all the
89 # shadow variables are set
90 self.capture_output()
91 self.release_output()
92
91
93 #--------------------------------------------------------------------------
92 #--------------------------------------------------------------------------
94 # FrontEndBase interface
93 # FrontEndBase interface
95 #--------------------------------------------------------------------------
94 #--------------------------------------------------------------------------
96
95
97 def show_traceback(self):
96 def show_traceback(self):
98 """ Use ipython0 to capture the last traceback and display it.
97 """ Use ipython0 to capture the last traceback and display it.
99 """
98 """
100 self.capture_output()
99 self.capture_output()
101 self.ipython0.showtraceback()
100 self.ipython0.showtraceback()
102 self.release_output()
101 self.release_output()
103
102
104
103
105 def execute(self, python_string, raw_string=None):
104 def execute(self, python_string, raw_string=None):
106 self.capture_output()
105 self.capture_output()
107 LineFrontEndBase.execute(self, python_string,
106 LineFrontEndBase.execute(self, python_string,
108 raw_string=raw_string)
107 raw_string=raw_string)
109 self.release_output()
108 self.release_output()
110
109
111
110
111 def save_output_hooks(self):
112 """ Store all the output hooks we can think of, to be able to
113 restore them.
114
115 We need to do this early, as starting the ipython0 instance will
116 screw ouput hooks.
117 """
118 self.__old_cout_write = Term.cout.write
119 self.__old_cerr_write = Term.cerr.write
120 self.__old_stdout = sys.stdout
121 self.__old_stderr= sys.stderr
122 self.__old_help_output = pydoc.help.output
123 self.__old_display_hook = sys.displayhook
124
125
112 def capture_output(self):
126 def capture_output(self):
113 """ Capture all the output mechanisms we can think of.
127 """ Capture all the output mechanisms we can think of.
114 """
128 """
115 self.__old_cout_write = Term.cout.write
129 self.save_output_hooks()
116 self.__old_err_write = Term.cerr.write
117 Term.cout.write = self.write
130 Term.cout.write = self.write
118 Term.cerr.write = self.write
131 Term.cerr.write = self.write
119 self.__old_stdout = sys.stdout
120 self.__old_stderr= sys.stderr
121 sys.stdout = Term.cout
132 sys.stdout = Term.cout
122 sys.stderr = Term.cerr
133 sys.stderr = Term.cerr
123 self.__old_help_output = pydoc.help.output
124 pydoc.help.output = self.shell.output_trap.out
134 pydoc.help.output = self.shell.output_trap.out
125
135
126
136
127 def release_output(self):
137 def release_output(self):
128 """ Release all the different captures we have made.
138 """ Release all the different captures we have made.
129 """
139 """
130 Term.cout.write = self.__old_cout_write
140 Term.cout.write = self.__old_cout_write
131 Term.cerr.write = self.__old_err_write
141 Term.cerr.write = self.__old_cerr_write
132 sys.stdout = self.__old_stdout
142 sys.stdout = self.__old_stdout
133 sys.stderr = self.__old_stderr
143 sys.stderr = self.__old_stderr
134 pydoc.help.output = self.__old_help_output
144 pydoc.help.output = self.__old_help_output
145 sys.displayhook = self.__old_display_hook
135
146
136
147
137 def complete(self, line):
148 def complete(self, line):
138 word = line.split('\n')[-1].split(' ')[-1]
149 word = line.split('\n')[-1].split(' ')[-1]
139 completions = self.ipython0.complete(word)
150 completions = self.ipython0.complete(word)
140 # FIXME: The proper sort should be done in the complete method.
151 # FIXME: The proper sort should be done in the complete method.
141 key = lambda x: x.replace('_', '')
152 key = lambda x: x.replace('_', '')
142 completions.sort(key=key)
153 completions.sort(key=key)
143 if completions:
154 if completions:
144 prefix = common_prefix(completions)
155 prefix = common_prefix(completions)
145 line = line[:-len(word)] + prefix
156 line = line[:-len(word)] + prefix
146 return line, completions
157 return line, completions
147
158
148
159
149 #--------------------------------------------------------------------------
160 #--------------------------------------------------------------------------
150 # LineFrontEndBase interface
161 # LineFrontEndBase interface
151 #--------------------------------------------------------------------------
162 #--------------------------------------------------------------------------
152
163
153 def prefilter_input(self, input_string):
164 def prefilter_input(self, input_string):
154 """ Using IPython0 to prefilter the commands to turn them
165 """ Using IPython0 to prefilter the commands to turn them
155 in executable statements that are valid Python strings.
166 in executable statements that are valid Python strings.
156 """
167 """
157 input_string = LineFrontEndBase.prefilter_input(self, input_string)
168 input_string = LineFrontEndBase.prefilter_input(self, input_string)
158 filtered_lines = []
169 filtered_lines = []
159 # The IPython0 prefilters sometime produce output. We need to
170 # The IPython0 prefilters sometime produce output. We need to
160 # capture it.
171 # capture it.
161 self.capture_output()
172 self.capture_output()
162 self.last_result = dict(number=self.prompt_number)
173 self.last_result = dict(number=self.prompt_number)
163 try:
174 try:
164 for line in input_string.split('\n'):
175 for line in input_string.split('\n'):
165 filtered_lines.append(self.ipython0.prefilter(line, False))
176 filtered_lines.append(self.ipython0.prefilter(line, False))
166 except:
177 except:
167 # XXX: probably not the right thing to do.
178 # XXX: probably not the right thing to do.
168 self.ipython0.showsyntaxerror()
179 self.ipython0.showsyntaxerror()
169 self.after_execute()
180 self.after_execute()
170 finally:
181 finally:
171 self.release_output()
182 self.release_output()
172
183
173 filtered_string = '\n'.join(filtered_lines)
184 filtered_string = '\n'.join(filtered_lines)
174 return filtered_string
185 return filtered_string
175
186
176
187
177 #--------------------------------------------------------------------------
188 #--------------------------------------------------------------------------
178 # PrefilterFrontEnd interface
189 # PrefilterFrontEnd interface
179 #--------------------------------------------------------------------------
190 #--------------------------------------------------------------------------
180
191
181 def system_call(self, command_string):
192 def system_call(self, command_string):
182 """ Allows for frontend to define their own system call, to be
193 """ Allows for frontend to define their own system call, to be
183 able capture output and redirect input.
194 able capture output and redirect input.
184 """
195 """
185 return os.system(command_string)
196 return os.system(command_string)
186
197
187
198
188 def do_exit(self):
199 def do_exit(self):
189 """ Exit the shell, cleanup and save the history.
200 """ Exit the shell, cleanup and save the history.
190 """
201 """
191 self.ipython0.atexit_operations()
202 self.ipython0.atexit_operations()
192
203
@@ -1,129 +1,130 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Test process execution and IO redirection.
3 Test process execution and IO redirection.
4 """
4 """
5
5
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
15 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
16 from cStringIO import StringIO
16 from cStringIO import StringIO
17 import string
17 import string
18 import sys
18
19
19 class TestPrefilterFrontEnd(PrefilterFrontEnd):
20 class TestPrefilterFrontEnd(PrefilterFrontEnd):
20
21
21 input_prompt_template = string.Template('')
22 input_prompt_template = string.Template('')
22 output_prompt_template = string.Template('')
23 output_prompt_template = string.Template('')
23
24
24 def __init__(self):
25 def __init__(self):
25 self.out = StringIO()
26 self.out = StringIO()
26 PrefilterFrontEnd.__init__(self)
27 PrefilterFrontEnd.__init__(self)
27
28
28 def write(self, string):
29 def write(self, string):
29 self.out.write(string)
30 self.out.write(string)
30
31
31 def _on_enter(self):
32 def _on_enter(self):
32 self.input_buffer += '\n'
33 self.input_buffer += '\n'
33 PrefilterFrontEnd._on_enter(self)
34 PrefilterFrontEnd._on_enter(self)
34
35
35
36
36 def test_execution():
37 def test_execution():
37 """ Test execution of a command.
38 """ Test execution of a command.
38 """
39 """
39 f = TestPrefilterFrontEnd()
40 f = TestPrefilterFrontEnd()
40 f.input_buffer = 'print 1\n'
41 f.input_buffer = 'print 1\n'
41 f._on_enter()
42 f._on_enter()
42 assert f.out.getvalue() == '1\n'
43 assert f.out.getvalue() == '1\n'
43
44
44
45
45 def test_multiline():
46 def test_multiline():
46 """ Test execution of a multiline command.
47 """ Test execution of a multiline command.
47 """
48 """
48 f = TestPrefilterFrontEnd()
49 f = TestPrefilterFrontEnd()
49 f.input_buffer = 'if True:'
50 f.input_buffer = 'if True:'
50 f._on_enter()
51 f._on_enter()
51 f.input_buffer += 'print 1'
52 f.input_buffer += 'print 1'
52 f._on_enter()
53 f._on_enter()
53 assert f.out.getvalue() == ''
54 assert f.out.getvalue() == ''
54 f._on_enter()
55 f._on_enter()
55 assert f.out.getvalue() == '1\n'
56 assert f.out.getvalue() == '1\n'
56 f = TestPrefilterFrontEnd()
57 f = TestPrefilterFrontEnd()
57 f.input_buffer='(1 +'
58 f.input_buffer='(1 +'
58 f._on_enter()
59 f._on_enter()
59 f.input_buffer += '0)'
60 f.input_buffer += '0)'
60 f._on_enter()
61 f._on_enter()
61 assert f.out.getvalue() == ''
62 assert f.out.getvalue() == ''
62 f._on_enter()
63 f._on_enter()
63 assert f.out.getvalue() == '1\n'
64 assert f.out.getvalue() == '1\n'
64
65
65
66
66 def test_capture():
67 def test_capture():
67 """ Test the capture of output in different channels.
68 """ Test the capture of output in different channels.
68 """
69 """
69 # Test on the OS-level stdout, stderr.
70 # Test on the OS-level stdout, stderr.
70 f = TestPrefilterFrontEnd()
71 f = TestPrefilterFrontEnd()
71 f.input_buffer = \
72 f.input_buffer = \
72 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
73 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
73 f._on_enter()
74 f._on_enter()
74 assert f.out.getvalue() == '1'
75 assert f.out.getvalue() == '1'
75 f = TestPrefilterFrontEnd()
76 f = TestPrefilterFrontEnd()
76 f.input_buffer = \
77 f.input_buffer = \
77 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
78 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
78 f._on_enter()
79 f._on_enter()
79 assert f.out.getvalue() == '1'
80 assert f.out.getvalue() == '1'
80
81
81
82
82 def test_magic():
83 def test_magic():
83 """ Test the magic expansion and history.
84 """ Test the magic expansion and history.
84
85
85 This test is fairly fragile and will break when magics change.
86 This test is fairly fragile and will break when magics change.
86 """
87 """
87 f = TestPrefilterFrontEnd()
88 f = TestPrefilterFrontEnd()
88 f.input_buffer += '%who\n'
89 f.input_buffer += '%who\n'
89 f._on_enter()
90 f._on_enter()
90 assert f.out.getvalue() == 'Interactive namespace is empty.\n'
91 assert f.out.getvalue() == 'Interactive namespace is empty.\n'
91
92
92
93
93 def test_help():
94 def test_help():
94 """ Test object inspection.
95 """ Test object inspection.
95 """
96 """
96 f = TestPrefilterFrontEnd()
97 f = TestPrefilterFrontEnd()
97 f.input_buffer += "def f():"
98 f.input_buffer += "def f():"
98 f._on_enter()
99 f._on_enter()
99 f.input_buffer += "'foobar'"
100 f.input_buffer += "'foobar'"
100 f._on_enter()
101 f._on_enter()
101 f.input_buffer += "pass"
102 f.input_buffer += "pass"
102 f._on_enter()
103 f._on_enter()
103 f._on_enter()
104 f._on_enter()
104 f.input_buffer += "f?"
105 f.input_buffer += "f?"
105 f._on_enter()
106 f._on_enter()
106 assert f.out.getvalue().split()[-1] == 'foobar'
107 assert f.out.getvalue().split()[-1] == 'foobar'
107
108
108
109
109 def test_completion():
110 def test_completion():
110 """ Test command-line completion.
111 """ Test command-line completion.
111 """
112 """
112 f = TestPrefilterFrontEnd()
113 f = TestPrefilterFrontEnd()
113 f.input_buffer = 'zzza = 1'
114 f.input_buffer = 'zzza = 1'
114 f._on_enter()
115 f._on_enter()
115 f.input_buffer = 'zzzb = 2'
116 f.input_buffer = 'zzzb = 2'
116 f._on_enter()
117 f._on_enter()
117 f.input_buffer = 'zz'
118 f.input_buffer = 'zz'
118 f.complete_current_input()
119 f.complete_current_input()
119 assert f.out.getvalue() == '\nzzza zzzb '
120 assert f.out.getvalue() == '\nzzza zzzb '
120 assert f.input_buffer == 'zzz'
121 assert f.input_buffer == 'zzz'
121
122
122
123
123 if __name__ == '__main__':
124 if __name__ == '__main__':
124 test_magic()
125 test_magic()
125 test_help()
126 test_help()
126 test_execution()
127 test_execution()
127 test_multiline()
128 test_multiline()
128 test_capture()
129 test_capture()
129 test_completion()
130 test_completion()
@@ -1,415 +1,413 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 sys
26 import sys
27 LINESEP = '\n'
27 LINESEP = '\n'
28 if sys.platform == 'win32':
28 if sys.platform == 'win32':
29 LINESEP = '\n\r'
29 LINESEP = '\n\r'
30
30
31 import re
31 import re
32
32
33 # FIXME: Need to provide an API for non user-generated display on the
33 # FIXME: Need to provide an API for non user-generated display on the
34 # screen: this should not be editable by the user.
34 # screen: this should not be editable by the user.
35
35
36 _DEFAULT_SIZE = 10
36 _DEFAULT_SIZE = 10
37
37
38 _DEFAULT_STYLE = {
38 _DEFAULT_STYLE = {
39 'stdout' : 'fore:#0000FF',
39 'stdout' : 'fore:#0000FF',
40 'stderr' : 'fore:#007f00',
40 'stderr' : 'fore:#007f00',
41 'trace' : 'fore:#FF0000',
41 'trace' : 'fore:#FF0000',
42
42
43 'default' : 'size:%d' % _DEFAULT_SIZE,
43 'default' : 'size:%d' % _DEFAULT_SIZE,
44 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
44 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
45 'bracebad' : 'fore:#000000,back:#FF0000,bold',
45 'bracebad' : 'fore:#000000,back:#FF0000,bold',
46
46
47 # properties for the various Python lexer styles
47 # properties for the various Python lexer styles
48 'comment' : 'fore:#007F00',
48 'comment' : 'fore:#007F00',
49 'number' : 'fore:#007F7F',
49 'number' : 'fore:#007F7F',
50 'string' : 'fore:#7F007F,italic',
50 'string' : 'fore:#7F007F,italic',
51 'char' : 'fore:#7F007F,italic',
51 'char' : 'fore:#7F007F,italic',
52 'keyword' : 'fore:#00007F,bold',
52 'keyword' : 'fore:#00007F,bold',
53 'triple' : 'fore:#7F0000',
53 'triple' : 'fore:#7F0000',
54 'tripledouble' : 'fore:#7F0000',
54 'tripledouble' : 'fore:#7F0000',
55 'class' : 'fore:#0000FF,bold,underline',
55 'class' : 'fore:#0000FF,bold,underline',
56 'def' : 'fore:#007F7F,bold',
56 'def' : 'fore:#007F7F,bold',
57 'operator' : 'bold'
57 'operator' : 'bold'
58 }
58 }
59
59
60 # new style numbers
60 # new style numbers
61 _STDOUT_STYLE = 15
61 _STDOUT_STYLE = 15
62 _STDERR_STYLE = 16
62 _STDERR_STYLE = 16
63 _TRACE_STYLE = 17
63 _TRACE_STYLE = 17
64
64
65
65
66 # system colors
66 # system colors
67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
68
68
69 #-------------------------------------------------------------------------------
69 #-------------------------------------------------------------------------------
70 # The console widget class
70 # The console widget class
71 #-------------------------------------------------------------------------------
71 #-------------------------------------------------------------------------------
72 class ConsoleWidget(editwindow.EditWindow):
72 class ConsoleWidget(editwindow.EditWindow):
73 """ Specialized styled text control view for console-like workflow.
73 """ Specialized styled text control view for console-like workflow.
74
74
75 This widget is mainly interested in dealing with the prompt and
75 This widget is mainly interested in dealing with the prompt and
76 keeping the cursor inside the editing line.
76 keeping the cursor inside the editing line.
77 """
77 """
78
78
79 # This is where the title captured from the ANSI escape sequences are
79 # This is where the title captured from the ANSI escape sequences are
80 # stored.
80 # stored.
81 title = 'Console'
81 title = 'Console'
82
82
83 # The buffer being edited.
83 # The buffer being edited.
84 def _set_input_buffer(self, string):
84 def _set_input_buffer(self, string):
85 self.SetSelection(self.current_prompt_pos, self.GetLength())
85 self.SetSelection(self.current_prompt_pos, self.GetLength())
86 self.ReplaceSelection(string)
86 self.ReplaceSelection(string)
87 self.GotoPos(self.GetLength())
87 self.GotoPos(self.GetLength())
88
88
89 def _get_input_buffer(self):
89 def _get_input_buffer(self):
90 """ Returns the text in current edit buffer.
90 """ Returns the text in current edit buffer.
91 """
91 """
92 input_buffer = self.GetTextRange(self.current_prompt_pos,
92 input_buffer = self.GetTextRange(self.current_prompt_pos,
93 self.GetLength())
93 self.GetLength())
94 input_buffer = input_buffer.replace(LINESEP, '\n')
94 input_buffer = input_buffer.replace(LINESEP, '\n')
95 return input_buffer
95 return input_buffer
96
96
97 input_buffer = property(_get_input_buffer, _set_input_buffer)
97 input_buffer = property(_get_input_buffer, _set_input_buffer)
98
98
99 style = _DEFAULT_STYLE.copy()
99 style = _DEFAULT_STYLE.copy()
100
100
101 # Translation table from ANSI escape sequences to color. Override
101 # Translation table from ANSI escape sequences to color. Override
102 # this to specify your colors.
102 # this to specify your colors.
103 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
103 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
104 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
104 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
105 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
105 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
106 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
106 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
107 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
107 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
108 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
108 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
109 '1;34': [12, 'LIGHT BLUE'], '1;35':
109 '1;34': [12, 'LIGHT BLUE'], '1;35':
110 [13, 'MEDIUM VIOLET RED'],
110 [13, 'MEDIUM VIOLET RED'],
111 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
111 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
112
112
113 # The color of the carret (call _apply_style() after setting)
113 # The color of the carret (call _apply_style() after setting)
114 carret_color = 'BLACK'
114 carret_color = 'BLACK'
115
115
116 #--------------------------------------------------------------------------
116 #--------------------------------------------------------------------------
117 # Public API
117 # Public API
118 #--------------------------------------------------------------------------
118 #--------------------------------------------------------------------------
119
119
120 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
120 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
121 size=wx.DefaultSize, style=0, ):
121 size=wx.DefaultSize, style=0, ):
122 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
122 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
123 self._configure_scintilla()
123 self._configure_scintilla()
124
124
125 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
125 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
126 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
126 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
127
127
128
128
129 def write(self, text, refresh=True):
129 def write(self, text, refresh=True):
130 """ Write given text to buffer, while translating the ansi escape
130 """ Write given text to buffer, while translating the ansi escape
131 sequences.
131 sequences.
132 """
132 """
133 # XXX: do not put print statements to sys.stdout/sys.stderr in
133 # XXX: do not put print statements to sys.stdout/sys.stderr in
134 # this method, the print statements will call this method, as
134 # this method, the print statements will call this method, as
135 # you will end up with an infinit loop
135 # you will end up with an infinit loop
136 if self.debug:
137 print >>sys.__stderr__, text
138 title = self.title_pat.split(text)
136 title = self.title_pat.split(text)
139 if len(title)>1:
137 if len(title)>1:
140 self.title = title[-2]
138 self.title = title[-2]
141
139
142 text = self.title_pat.sub('', text)
140 text = self.title_pat.sub('', text)
143 segments = self.color_pat.split(text)
141 segments = self.color_pat.split(text)
144 segment = segments.pop(0)
142 segment = segments.pop(0)
145 self.GotoPos(self.GetLength())
143 self.GotoPos(self.GetLength())
146 self.StartStyling(self.GetLength(), 0xFF)
144 self.StartStyling(self.GetLength(), 0xFF)
147 try:
145 try:
148 self.AppendText(segment)
146 self.AppendText(segment)
149 except UnicodeDecodeError:
147 except UnicodeDecodeError:
150 # XXX: Do I really want to skip the exception?
148 # XXX: Do I really want to skip the exception?
151 pass
149 pass
152
150
153 if segments:
151 if segments:
154 for ansi_tag, text in zip(segments[::2], segments[1::2]):
152 for ansi_tag, text in zip(segments[::2], segments[1::2]):
155 self.StartStyling(self.GetLength(), 0xFF)
153 self.StartStyling(self.GetLength(), 0xFF)
156 try:
154 try:
157 self.AppendText(text)
155 self.AppendText(text)
158 except UnicodeDecodeError:
156 except UnicodeDecodeError:
159 # XXX: Do I really want to skip the exception?
157 # XXX: Do I really want to skip the exception?
160 pass
158 pass
161
159
162 if ansi_tag not in self.ANSI_STYLES:
160 if ansi_tag not in self.ANSI_STYLES:
163 style = 0
161 style = 0
164 else:
162 else:
165 style = self.ANSI_STYLES[ansi_tag][0]
163 style = self.ANSI_STYLES[ansi_tag][0]
166
164
167 self.SetStyling(len(text), style)
165 self.SetStyling(len(text), style)
168
166
169 self.GotoPos(self.GetLength())
167 self.GotoPos(self.GetLength())
170 if refresh:
168 if refresh:
171 wx.Yield()
169 wx.Yield()
172
170
173
171
174 def new_prompt(self, prompt):
172 def new_prompt(self, prompt):
175 """ Prints a prompt at start of line, and move the start of the
173 """ Prints a prompt at start of line, and move the start of the
176 current block there.
174 current block there.
177
175
178 The prompt can be given with ascii escape sequences.
176 The prompt can be given with ascii escape sequences.
179 """
177 """
180 self.write(prompt)
178 self.write(prompt)
181 # now we update our cursor giving end of prompt
179 # now we update our cursor giving end of prompt
182 self.current_prompt_pos = self.GetLength()
180 self.current_prompt_pos = self.GetLength()
183 self.current_prompt_line = self.GetCurrentLine()
181 self.current_prompt_line = self.GetCurrentLine()
184 wx.Yield()
182 wx.Yield()
185 self.EnsureCaretVisible()
183 self.EnsureCaretVisible()
186
184
187
185
188 def scroll_to_bottom(self):
186 def scroll_to_bottom(self):
189 maxrange = self.GetScrollRange(wx.VERTICAL)
187 maxrange = self.GetScrollRange(wx.VERTICAL)
190 self.ScrollLines(maxrange)
188 self.ScrollLines(maxrange)
191
189
192
190
193 def pop_completion(self, possibilities, offset=0):
191 def pop_completion(self, possibilities, offset=0):
194 """ Pops up an autocompletion menu. Offset is the offset
192 """ Pops up an autocompletion menu. Offset is the offset
195 in characters of the position at which the menu should
193 in characters of the position at which the menu should
196 appear, relativ to the cursor.
194 appear, relativ to the cursor.
197 """
195 """
198 self.AutoCompSetIgnoreCase(False)
196 self.AutoCompSetIgnoreCase(False)
199 self.AutoCompSetAutoHide(False)
197 self.AutoCompSetAutoHide(False)
200 self.AutoCompSetMaxHeight(len(possibilities))
198 self.AutoCompSetMaxHeight(len(possibilities))
201 self.AutoCompShow(offset, " ".join(possibilities))
199 self.AutoCompShow(offset, " ".join(possibilities))
202
200
203
201
204 def get_line_width(self):
202 def get_line_width(self):
205 """ Return the width of the line in characters.
203 """ Return the width of the line in characters.
206 """
204 """
207 return self.GetSize()[0]/self.GetCharWidth()
205 return self.GetSize()[0]/self.GetCharWidth()
208
206
209
207
210 #--------------------------------------------------------------------------
208 #--------------------------------------------------------------------------
211 # Private API
209 # Private API
212 #--------------------------------------------------------------------------
210 #--------------------------------------------------------------------------
213
211
214 def _apply_style(self):
212 def _apply_style(self):
215 """ Applies the colors for the different text elements and the
213 """ Applies the colors for the different text elements and the
216 carret.
214 carret.
217 """
215 """
218 self.SetCaretForeground(self.carret_color)
216 self.SetCaretForeground(self.carret_color)
219
217
220 #self.StyleClearAll()
218 #self.StyleClearAll()
221 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
219 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
222 "fore:#FF0000,back:#0000FF,bold")
220 "fore:#FF0000,back:#0000FF,bold")
223 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
221 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
224 "fore:#000000,back:#FF0000,bold")
222 "fore:#000000,back:#FF0000,bold")
225
223
226 for style in self.ANSI_STYLES.values():
224 for style in self.ANSI_STYLES.values():
227 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
225 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
228
226
229
227
230 def _configure_scintilla(self):
228 def _configure_scintilla(self):
231 self.SetEOLMode(stc.STC_EOL_LF)
229 self.SetEOLMode(stc.STC_EOL_LF)
232
230
233 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
231 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
234 # the widget
232 # the widget
235 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
233 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
236 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
234 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
237 # Also allow Ctrl Shift "=" for poor non US keyboard users.
235 # Also allow Ctrl Shift "=" for poor non US keyboard users.
238 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
236 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
239 stc.STC_CMD_ZOOMIN)
237 stc.STC_CMD_ZOOMIN)
240
238
241 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
239 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
242 # stc.STC_CMD_PAGEUP)
240 # stc.STC_CMD_PAGEUP)
243
241
244 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
242 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
245 # stc.STC_CMD_PAGEDOWN)
243 # stc.STC_CMD_PAGEDOWN)
246
244
247 # Keys: we need to clear some of the keys the that don't play
245 # Keys: we need to clear some of the keys the that don't play
248 # well with a console.
246 # well with a console.
249 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
247 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
250 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
248 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
251 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
249 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
252
250
253
251
254 self.SetEOLMode(stc.STC_EOL_CRLF)
252 self.SetEOLMode(stc.STC_EOL_CRLF)
255 self.SetWrapMode(stc.STC_WRAP_CHAR)
253 self.SetWrapMode(stc.STC_WRAP_CHAR)
256 self.SetWrapMode(stc.STC_WRAP_WORD)
254 self.SetWrapMode(stc.STC_WRAP_WORD)
257 self.SetBufferedDraw(True)
255 self.SetBufferedDraw(True)
258 self.SetUseAntiAliasing(True)
256 self.SetUseAntiAliasing(True)
259 self.SetLayoutCache(stc.STC_CACHE_PAGE)
257 self.SetLayoutCache(stc.STC_CACHE_PAGE)
260 self.SetUndoCollection(False)
258 self.SetUndoCollection(False)
261 self.SetUseTabs(True)
259 self.SetUseTabs(True)
262 self.SetIndent(4)
260 self.SetIndent(4)
263 self.SetTabWidth(4)
261 self.SetTabWidth(4)
264
262
265 self.EnsureCaretVisible()
263 self.EnsureCaretVisible()
266 # we don't want scintilla's autocompletion to choose
264 # we don't want scintilla's autocompletion to choose
267 # automaticaly out of a single choice list, as we pop it up
265 # automaticaly out of a single choice list, as we pop it up
268 # automaticaly
266 # automaticaly
269 self.AutoCompSetChooseSingle(False)
267 self.AutoCompSetChooseSingle(False)
270 self.AutoCompSetMaxHeight(10)
268 self.AutoCompSetMaxHeight(10)
271
269
272 self.SetMargins(3, 3) #text is moved away from border with 3px
270 self.SetMargins(3, 3) #text is moved away from border with 3px
273 # Suppressing Scintilla margins
271 # Suppressing Scintilla margins
274 self.SetMarginWidth(0, 0)
272 self.SetMarginWidth(0, 0)
275 self.SetMarginWidth(1, 0)
273 self.SetMarginWidth(1, 0)
276 self.SetMarginWidth(2, 0)
274 self.SetMarginWidth(2, 0)
277
275
278 self._apply_style()
276 self._apply_style()
279
277
280 # Xterm escape sequences
278 # Xterm escape sequences
281 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
279 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
282 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
280 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
283
281
284 #self.SetEdgeMode(stc.STC_EDGE_LINE)
282 #self.SetEdgeMode(stc.STC_EDGE_LINE)
285 #self.SetEdgeColumn(80)
283 #self.SetEdgeColumn(80)
286
284
287 # styles
285 # styles
288 p = self.style
286 p = self.style
289 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
287 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
290 self.StyleClearAll()
288 self.StyleClearAll()
291 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
289 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
292 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
290 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
293 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
291 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
294
292
295 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
293 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
296 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
294 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
297 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
295 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
298 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
296 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
299 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
297 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
300 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
298 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
301 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
299 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
302 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
300 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
303 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
301 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
304 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
302 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
305 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
303 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
306 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
304 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
307 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
305 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
308 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
306 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
309
307
310
308
311 def _on_key_down(self, event, skip=True):
309 def _on_key_down(self, event, skip=True):
312 """ Key press callback used for correcting behavior for
310 """ Key press callback used for correcting behavior for
313 console-like interfaces: the cursor is constraint to be after
311 console-like interfaces: the cursor is constraint to be after
314 the last prompt.
312 the last prompt.
315
313
316 Return True if event as been catched.
314 Return True if event as been catched.
317 """
315 """
318 catched = True
316 catched = True
319 # Intercept some specific keys.
317 # Intercept some specific keys.
320 if event.KeyCode == ord('L') and event.ControlDown() :
318 if event.KeyCode == ord('L') and event.ControlDown() :
321 self.scroll_to_bottom()
319 self.scroll_to_bottom()
322 elif event.KeyCode == ord('K') and event.ControlDown() :
320 elif event.KeyCode == ord('K') and event.ControlDown() :
323 self.input_buffer = ''
321 self.input_buffer = ''
324 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
322 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
325 self.ScrollPages(-1)
323 self.ScrollPages(-1)
326 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
324 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
327 self.ScrollPages(1)
325 self.ScrollPages(1)
328 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
326 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
329 self.ScrollLines(-1)
327 self.ScrollLines(-1)
330 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
328 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
331 self.ScrollLines(1)
329 self.ScrollLines(1)
332 else:
330 else:
333 catched = False
331 catched = False
334
332
335 if self.AutoCompActive():
333 if self.AutoCompActive():
336 event.Skip()
334 event.Skip()
337 else:
335 else:
338 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
336 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
339 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
337 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
340 catched = True
338 catched = True
341 self.CallTipCancel()
339 self.CallTipCancel()
342 self.write('\n')
340 self.write('\n')
343 # Under windows scintilla seems to be doing funny stuff to the
341 # Under windows scintilla seems to be doing funny stuff to the
344 # line returns here, but the getter for input_buffer filters
342 # line returns here, but the getter for input_buffer filters
345 # this out.
343 # this out.
346 if sys.platform == 'win32':
344 if sys.platform == 'win32':
347 self.input_buffer = self.input_buffer
345 self.input_buffer = self.input_buffer
348 self._on_enter()
346 self._on_enter()
349
347
350 elif event.KeyCode == wx.WXK_HOME:
348 elif event.KeyCode == wx.WXK_HOME:
351 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
349 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
352 self.GotoPos(self.current_prompt_pos)
350 self.GotoPos(self.current_prompt_pos)
353 catched = True
351 catched = True
354
352
355 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
353 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
356 # FIXME: This behavior is not ideal: if the selection
354 # FIXME: This behavior is not ideal: if the selection
357 # is already started, it will jump.
355 # is already started, it will jump.
358 self.SetSelectionStart(self.current_prompt_pos)
356 self.SetSelectionStart(self.current_prompt_pos)
359 self.SetSelectionEnd(self.GetCurrentPos())
357 self.SetSelectionEnd(self.GetCurrentPos())
360 catched = True
358 catched = True
361
359
362 elif event.KeyCode == wx.WXK_UP:
360 elif event.KeyCode == wx.WXK_UP:
363 if self.GetCurrentLine() > self.current_prompt_line:
361 if self.GetCurrentLine() > self.current_prompt_line:
364 if self.GetCurrentLine() == self.current_prompt_line + 1 \
362 if self.GetCurrentLine() == self.current_prompt_line + 1 \
365 and self.GetColumn(self.GetCurrentPos()) < \
363 and self.GetColumn(self.GetCurrentPos()) < \
366 self.GetColumn(self.current_prompt_pos):
364 self.GetColumn(self.current_prompt_pos):
367 self.GotoPos(self.current_prompt_pos)
365 self.GotoPos(self.current_prompt_pos)
368 else:
366 else:
369 event.Skip()
367 event.Skip()
370 catched = True
368 catched = True
371
369
372 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
370 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
373 if self.GetCurrentPos() > self.current_prompt_pos:
371 if self.GetCurrentPos() > self.current_prompt_pos:
374 event.Skip()
372 event.Skip()
375 catched = True
373 catched = True
376
374
377 if skip and not catched:
375 if skip and not catched:
378 # Put the cursor back in the edit region
376 # Put the cursor back in the edit region
379 if self.GetCurrentPos() < self.current_prompt_pos:
377 if self.GetCurrentPos() < self.current_prompt_pos:
380 self.GotoPos(self.current_prompt_pos)
378 self.GotoPos(self.current_prompt_pos)
381 else:
379 else:
382 event.Skip()
380 event.Skip()
383
381
384 return catched
382 return catched
385
383
386
384
387 def _on_key_up(self, event, skip=True):
385 def _on_key_up(self, event, skip=True):
388 """ If cursor is outside the editing region, put it back.
386 """ If cursor is outside the editing region, put it back.
389 """
387 """
390 event.Skip()
388 event.Skip()
391 if self.GetCurrentPos() < self.current_prompt_pos:
389 if self.GetCurrentPos() < self.current_prompt_pos:
392 self.GotoPos(self.current_prompt_pos)
390 self.GotoPos(self.current_prompt_pos)
393
391
394
392
395
393
396 if __name__ == '__main__':
394 if __name__ == '__main__':
397 # Some simple code to test the console widget.
395 # Some simple code to test the console widget.
398 class MainWindow(wx.Frame):
396 class MainWindow(wx.Frame):
399 def __init__(self, parent, id, title):
397 def __init__(self, parent, id, title):
400 wx.Frame.__init__(self, parent, id, title, size=(300,250))
398 wx.Frame.__init__(self, parent, id, title, size=(300,250))
401 self._sizer = wx.BoxSizer(wx.VERTICAL)
399 self._sizer = wx.BoxSizer(wx.VERTICAL)
402 self.console_widget = ConsoleWidget(self)
400 self.console_widget = ConsoleWidget(self)
403 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
401 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
404 self.SetSizer(self._sizer)
402 self.SetSizer(self._sizer)
405 self.SetAutoLayout(1)
403 self.SetAutoLayout(1)
406 self.Show(True)
404 self.Show(True)
407
405
408 app = wx.PySimpleApp()
406 app = wx.PySimpleApp()
409 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
407 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
410 w.SetSize((780, 460))
408 w.SetSize((780, 460))
411 w.Show()
409 w.Show()
412
410
413 app.MainLoop()
411 app.MainLoop()
414
412
415
413
@@ -1,78 +1,78 b''
1 """
1 """
2 Entry point for a simple application giving a graphical frontend to
2 Entry point for a simple application giving a graphical frontend to
3 ipython.
3 ipython.
4 """
4 """
5
5
6 import wx
6 import wx
7 from wx_frontend import WxController
7 from wx_frontend import WxController
8 import __builtin__
8 import __builtin__
9
9
10 class IPythonXController(WxController):
10 class IPythonXController(WxController):
11 """ Sub class of WxController that adds some application-specific
11 """ Sub class of WxController that adds some application-specific
12 bindings.
12 bindings.
13 """
13 """
14
14
15 debug = False
15 debug = True
16
16
17 def __init__(self, *args, **kwargs):
17 def __init__(self, *args, **kwargs):
18 WxController.__init__(self, *args, **kwargs)
18 WxController.__init__(self, *args, **kwargs)
19 self.ipython0.ask_exit = self.do_exit
19 self.ipython0.ask_exit = self.do_exit
20
20
21
21
22 def _on_key_down(self, event, skip=True):
22 def _on_key_down(self, event, skip=True):
23 # Intercept Ctrl-D to quit
23 # Intercept Ctrl-D to quit
24 if event.KeyCode == ord('D') and event.ControlDown() and \
24 if event.KeyCode == ord('D') and event.ControlDown() and \
25 self.input_buffer == '' and \
25 self.input_buffer == '' and \
26 self._input_state == 'readline':
26 self._input_state == 'readline':
27 wx.CallAfter(self.ask_exit)
27 wx.CallAfter(self.ask_exit)
28 else:
28 else:
29 WxController._on_key_down(self, event, skip=skip)
29 WxController._on_key_down(self, event, skip=skip)
30
30
31
31
32 def ask_exit(self):
32 def ask_exit(self):
33 """ Ask the user whether to exit.
33 """ Ask the user whether to exit.
34 """
34 """
35 self.write('\n')
35 self.write('\n')
36 self.capture_output()
36 self.capture_output()
37 self.ipython0.shell.exit()
37 self.ipython0.shell.exit()
38 self.release_output()
38 self.release_output()
39 wx.Yield()
39 wx.Yield()
40 if not self.ipython0.exit_now:
40 if not self.ipython0.exit_now:
41 self.new_prompt(self.input_prompt_template.substitute(
41 self.new_prompt(self.input_prompt_template.substitute(
42 number=self.last_result['number'] + 1))
42 number=self.last_result['number'] + 1))
43
43
44
44
45 def do_exit(self):
45 def do_exit(self):
46 """ Exits the interpreter, kills the windows.
46 """ Exits the interpreter, kills the windows.
47 """
47 """
48 WxController.do_exit(self)
48 WxController.do_exit(self)
49 self.release_output()
49 self.release_output()
50 wx.CallAfter(wx.Exit)
50 wx.CallAfter(wx.Exit)
51
51
52
52
53
53
54 class IPythonX(wx.Frame):
54 class IPythonX(wx.Frame):
55 """ Main frame of the IPythonX app.
55 """ Main frame of the IPythonX app.
56 """
56 """
57
57
58 def __init__(self, parent, id, title):
58 def __init__(self, parent, id, title):
59 wx.Frame.__init__(self, parent, id, title, size=(300,250))
59 wx.Frame.__init__(self, parent, id, title, size=(300,250))
60 self._sizer = wx.BoxSizer(wx.VERTICAL)
60 self._sizer = wx.BoxSizer(wx.VERTICAL)
61 self.shell = IPythonXController(self)
61 self.shell = IPythonXController(self)
62 self._sizer.Add(self.shell, 1, wx.EXPAND)
62 self._sizer.Add(self.shell, 1, wx.EXPAND)
63 self.SetSizer(self._sizer)
63 self.SetSizer(self._sizer)
64 self.SetAutoLayout(1)
64 self.SetAutoLayout(1)
65 self.Show(True)
65 self.Show(True)
66
66
67
67
68 def main():
68 def main():
69 app = wx.PySimpleApp()
69 app = wx.PySimpleApp()
70 frame = IPythonX(None, wx.ID_ANY, 'IPythonX')
70 frame = IPythonX(None, wx.ID_ANY, 'IPythonX')
71 frame.shell.SetFocus()
71 frame.shell.SetFocus()
72 frame.shell.app = app
72 frame.shell.app = app
73 frame.SetSize((680, 460))
73 frame.SetSize((680, 460))
74
74
75 app.MainLoop()
75 app.MainLoop()
76
76
77 if __name__ == '__main__':
77 if __name__ == '__main__':
78 main()
78 main()
@@ -1,451 +1,465 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 from time import sleep
28 from time import sleep
29 import sys
29 import sys
30 from threading import Lock
30 from threading import Lock
31 import string
31 import string
32
32
33 import wx
33 import wx
34 from wx import stc
34 from wx import stc
35
35
36 # Ipython-specific imports.
36 # Ipython-specific imports.
37 from IPython.frontend._process import PipedProcess
37 from IPython.frontend._process import PipedProcess
38 from console_widget import ConsoleWidget
38 from console_widget import ConsoleWidget
39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40
40
41 #-------------------------------------------------------------------------------
41 #-------------------------------------------------------------------------------
42 # Constants
42 # Constants
43 #-------------------------------------------------------------------------------
43 #-------------------------------------------------------------------------------
44
44
45 #_COMMAND_BG = '#FAFAF1' # Nice green
45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 _ERROR_BG = '#FFF1F1' # Nice red
47 _ERROR_BG = '#FFF1F1' # Nice red
48
48
49 _RUNNING_BUFFER_MARKER = 31
49 _COMPLETE_BUFFER_MARKER = 31
50 _ERROR_MARKER = 30
50 _ERROR_MARKER = 30
51 _INPUT_MARKER = 29
51
52
52 prompt_in1 = \
53 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 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
54
55
55 prompt_out = \
56 prompt_out = \
56 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
57
58
58 #-------------------------------------------------------------------------------
59 #-------------------------------------------------------------------------------
59 # Classes to implement the Wx frontend
60 # Classes to implement the Wx frontend
60 #-------------------------------------------------------------------------------
61 #-------------------------------------------------------------------------------
61 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 """Classes to provide a Wx frontend to the
63 """Classes to provide a Wx frontend to the
63 IPython.kernel.core.interpreter.
64 IPython.kernel.core.interpreter.
64
65
65 This class inherits from ConsoleWidget, that provides a console-like
66 This class inherits from ConsoleWidget, that provides a console-like
66 widget to provide a text-rendering widget suitable for a terminal.
67 widget to provide a text-rendering widget suitable for a terminal.
67 """
68 """
68
69
69 output_prompt_template = string.Template(prompt_out)
70 output_prompt_template = string.Template(prompt_out)
70
71
71 input_prompt_template = string.Template(prompt_in1)
72 input_prompt_template = string.Template(prompt_in1)
72
73
73 # Print debug info on what is happening to the console.
74 # Print debug info on what is happening to the console.
74 debug = True
75 debug = True
75
76
76 # The title of the terminal, as captured through the ANSI escape
77 # The title of the terminal, as captured through the ANSI escape
77 # sequences.
78 # sequences.
78 def _set_title(self, title):
79 def _set_title(self, title):
79 return self.Parent.SetTitle(title)
80 return self.Parent.SetTitle(title)
80
81
81 def _get_title(self):
82 def _get_title(self):
82 return self.Parent.GetTitle()
83 return self.Parent.GetTitle()
83
84
84 title = property(_get_title, _set_title)
85 title = property(_get_title, _set_title)
85
86
86
87
87 # The buffer being edited.
88 # The buffer being edited.
88 # We are duplicating the defination here because of multiple
89 # We are duplicating the defination here because of multiple
89 # inheritence
90 # inheritence
90 def _set_input_buffer(self, string):
91 def _set_input_buffer(self, string):
91 return ConsoleWidget._set_input_buffer(self, string)
92 return ConsoleWidget._set_input_buffer(self, string)
92
93
93 def _get_input_buffer(self):
94 def _get_input_buffer(self):
94 """ Returns the text in current edit buffer.
95 """ Returns the text in current edit buffer.
95 """
96 """
96 return ConsoleWidget._get_input_buffer(self)
97 return ConsoleWidget._get_input_buffer(self)
97
98
98 input_buffer = property(_get_input_buffer, _set_input_buffer)
99 input_buffer = property(_get_input_buffer, _set_input_buffer)
99
100
100
101
101 #--------------------------------------------------------------------------
102 #--------------------------------------------------------------------------
102 # Private Attributes
103 # Private Attributes
103 #--------------------------------------------------------------------------
104 #--------------------------------------------------------------------------
104
105
105 # A flag governing the behavior of the input. Can be:
106 # A flag governing the behavior of the input. Can be:
106 #
107 #
107 # 'readline' for readline-like behavior with a prompt
108 # 'readline' for readline-like behavior with a prompt
108 # and an edit buffer.
109 # and an edit buffer.
109 # 'subprocess' for sending the raw input directly to a
110 # 'subprocess' for sending the raw input directly to a
110 # subprocess.
111 # subprocess.
111 # 'buffering' for buffering of the input, that will be used
112 # 'buffering' for buffering of the input, that will be used
112 # when the input state switches back to another state.
113 # when the input state switches back to another state.
113 _input_state = 'readline'
114 _input_state = 'readline'
114
115
115 # Attribute to store reference to the pipes of a subprocess, if we
116 # Attribute to store reference to the pipes of a subprocess, if we
116 # are running any.
117 # are running any.
117 _running_process = False
118 _running_process = False
118
119
119 # A queue for writing fast streams to the screen without flooding the
120 # A queue for writing fast streams to the screen without flooding the
120 # event loop
121 # event loop
121 _out_buffer = []
122 _out_buffer = []
122
123
123 # A lock to lock the _out_buffer to make sure we don't empty it
124 # A lock to lock the _out_buffer to make sure we don't empty it
124 # while it is being swapped
125 # while it is being swapped
125 _out_buffer_lock = Lock()
126 _out_buffer_lock = Lock()
126
127
128 _markers = dict()
129
127 #--------------------------------------------------------------------------
130 #--------------------------------------------------------------------------
128 # Public API
131 # Public API
129 #--------------------------------------------------------------------------
132 #--------------------------------------------------------------------------
130
133
131 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
134 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
132 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
135 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
133 *args, **kwds):
136 *args, **kwds):
134 """ Create Shell instance.
137 """ Create Shell instance.
135 """
138 """
136 ConsoleWidget.__init__(self, parent, id, pos, size, style)
139 ConsoleWidget.__init__(self, parent, id, pos, size, style)
137 PrefilterFrontEnd.__init__(self)
140 PrefilterFrontEnd.__init__(self)
138
141
139 # Marker for running buffer.
142 # Marker for complete buffer.
140 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
143 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
141 background=_RUNNING_BUFFER_BG)
144 background=_COMPLETE_BUFFER_BG)
145 # Marker for current input buffer.
146 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
147 background=_INPUT_BUFFER_BG)
142 # Marker for tracebacks.
148 # Marker for tracebacks.
143 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
149 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
144 background=_ERROR_BG)
150 background=_ERROR_BG)
145
151
146 # A time for flushing the write buffer
152 # A time for flushing the write buffer
147 BUFFER_FLUSH_TIMER_ID = 100
153 BUFFER_FLUSH_TIMER_ID = 100
148 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
154 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
149 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
155 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
150
156
157 # Inject self in namespace, for debug
158 if self.debug:
159 self.shell.user_ns['self'] = self
160
151
161
152 def raw_input(self, prompt):
162 def raw_input(self, prompt):
153 """ A replacement from python's raw_input.
163 """ A replacement from python's raw_input.
154 """
164 """
155 self.new_prompt(prompt)
165 self.new_prompt(prompt)
156 self.waiting = True
166 self.waiting = True
157 self.__old_on_enter = self._on_enter
167 self.__old_on_enter = self._on_enter
158 def my_on_enter():
168 def my_on_enter():
159 self.waiting = False
169 self.waiting = False
160 self._on_enter = my_on_enter
170 self._on_enter = my_on_enter
161 # XXX: Busy waiting, ugly.
171 # XXX: Busy waiting, ugly.
162 while self.waiting:
172 while self.waiting:
163 wx.Yield()
173 wx.Yield()
164 sleep(0.1)
174 sleep(0.1)
165 self._on_enter = self.__old_on_enter
175 self._on_enter = self.__old_on_enter
166 self._input_state = 'buffering'
176 self._input_state = 'buffering'
167 return self.input_buffer.rstrip('\n')
177 return self.input_buffer.rstrip('\n')
168
178
169
179
170 def system_call(self, command_string):
180 def system_call(self, command_string):
171 self._input_state = 'subprocess'
181 self._input_state = 'subprocess'
172 self._running_process = PipedProcess(command_string,
182 self._running_process = PipedProcess(command_string,
173 out_callback=self.buffered_write,
183 out_callback=self.buffered_write,
174 end_callback = self._end_system_call)
184 end_callback = self._end_system_call)
175 self._running_process.start()
185 self._running_process.start()
176 # XXX: another one of these polling loops to have a blocking
186 # XXX: another one of these polling loops to have a blocking
177 # call
187 # call
178 wx.Yield()
188 wx.Yield()
179 while self._running_process:
189 while self._running_process:
180 wx.Yield()
190 wx.Yield()
181 sleep(0.1)
191 sleep(0.1)
182 # Be sure to flush the buffer.
192 # Be sure to flush the buffer.
183 self._buffer_flush(event=None)
193 self._buffer_flush(event=None)
184
194
185
195
186 def do_calltip(self):
196 def do_calltip(self):
187 """ Analyse current and displays useful calltip for it.
197 """ Analyse current and displays useful calltip for it.
188 """
198 """
189 if self.debug:
199 if self.debug:
190 print >>sys.__stdout__, "do_calltip"
200 print >>sys.__stdout__, "do_calltip"
191 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
201 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
192 symbol = self.input_buffer
202 symbol = self.input_buffer
193 symbol_string = separators.split(symbol)[-1]
203 symbol_string = separators.split(symbol)[-1]
194 base_symbol_string = symbol_string.split('.')[0]
204 base_symbol_string = symbol_string.split('.')[0]
195 if base_symbol_string in self.shell.user_ns:
205 if base_symbol_string in self.shell.user_ns:
196 symbol = self.shell.user_ns[base_symbol_string]
206 symbol = self.shell.user_ns[base_symbol_string]
197 elif base_symbol_string in self.shell.user_global_ns:
207 elif base_symbol_string in self.shell.user_global_ns:
198 symbol = self.shell.user_global_ns[base_symbol_string]
208 symbol = self.shell.user_global_ns[base_symbol_string]
199 elif base_symbol_string in __builtin__.__dict__:
209 elif base_symbol_string in __builtin__.__dict__:
200 symbol = __builtin__.__dict__[base_symbol_string]
210 symbol = __builtin__.__dict__[base_symbol_string]
201 else:
211 else:
202 return False
212 return False
203 for name in symbol_string.split('.')[1:] + ['__doc__']:
213 for name in symbol_string.split('.')[1:] + ['__doc__']:
204 symbol = getattr(symbol, name)
214 symbol = getattr(symbol, name)
205 try:
215 try:
206 self.AutoCompCancel()
216 self.AutoCompCancel()
207 wx.Yield()
217 wx.Yield()
208 self.CallTipShow(self.GetCurrentPos(), symbol)
218 self.CallTipShow(self.GetCurrentPos(), symbol)
209 except:
219 except:
210 # The retrieve symbol couldn't be converted to a string
220 # The retrieve symbol couldn't be converted to a string
211 pass
221 pass
212
222
213
223
214 def _popup_completion(self, create=False):
224 def _popup_completion(self, create=False):
215 """ Updates the popup completion menu if it exists. If create is
225 """ Updates the popup completion menu if it exists. If create is
216 true, open the menu.
226 true, open the menu.
217 """
227 """
218 if self.debug:
228 if self.debug:
219 print >>sys.__stdout__, "_popup_completion",
229 print >>sys.__stdout__, "_popup_completion",
220 line = self.input_buffer
230 line = self.input_buffer
221 if (self.AutoCompActive() and not line[-1] == '.') \
231 if (self.AutoCompActive() and not line[-1] == '.') \
222 or create==True:
232 or create==True:
223 suggestion, completions = self.complete(line)
233 suggestion, completions = self.complete(line)
224 offset=0
234 offset=0
225 if completions:
235 if completions:
226 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
236 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
227 residual = complete_sep.split(line)[-1]
237 residual = complete_sep.split(line)[-1]
228 offset = len(residual)
238 offset = len(residual)
229 self.pop_completion(completions, offset=offset)
239 self.pop_completion(completions, offset=offset)
230 if self.debug:
240 if self.debug:
231 print >>sys.__stdout__, completions
241 print >>sys.__stdout__, completions
232
242
233
243
234 def buffered_write(self, text):
244 def buffered_write(self, text):
235 """ A write method for streams, that caches the stream in order
245 """ A write method for streams, that caches the stream in order
236 to avoid flooding the event loop.
246 to avoid flooding the event loop.
237
247
238 This can be called outside of the main loop, in separate
248 This can be called outside of the main loop, in separate
239 threads.
249 threads.
240 """
250 """
241 self._out_buffer_lock.acquire()
251 self._out_buffer_lock.acquire()
242 self._out_buffer.append(text)
252 self._out_buffer.append(text)
243 self._out_buffer_lock.release()
253 self._out_buffer_lock.release()
244 if not self._buffer_flush_timer.IsRunning():
254 if not self._buffer_flush_timer.IsRunning():
245 wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds
255 wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds
246
256
247
257
248 #--------------------------------------------------------------------------
258 #--------------------------------------------------------------------------
249 # LineFrontEnd interface
259 # LineFrontEnd interface
250 #--------------------------------------------------------------------------
260 #--------------------------------------------------------------------------
251
261
252 def execute(self, python_string, raw_string=None):
262 def execute(self, python_string, raw_string=None):
253 self._input_state = 'buffering'
263 self._input_state = 'buffering'
254 self.CallTipCancel()
264 self.CallTipCancel()
255 self._cursor = wx.BusyCursor()
265 self._cursor = wx.BusyCursor()
256 if raw_string is None:
266 if raw_string is None:
257 raw_string = python_string
267 raw_string = python_string
258 end_line = self.current_prompt_line \
268 end_line = self.current_prompt_line \
259 + max(1, len(raw_string.split('\n'))-1)
269 + max(1, len(raw_string.split('\n'))-1)
260 for i in range(self.current_prompt_line, end_line):
270 for i in range(self.current_prompt_line, end_line):
261 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
271 if i in self._markers:
272 self.MarkerDeleteHandle(self._markers[i])
273 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
262 # Update the display:
274 # Update the display:
263 wx.Yield()
275 wx.Yield()
264 self.GotoPos(self.GetLength())
276 self.GotoPos(self.GetLength())
265 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
277 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
266
278
267
279
268 def capture_output(self):
280 def capture_output(self):
269 self.__old_raw_input = __builtin__.raw_input
281 self.__old_raw_input = __builtin__.raw_input
270 __builtin__.raw_input = self.raw_input
282 __builtin__.raw_input = self.raw_input
271 PrefilterFrontEnd.capture_output(self)
283 PrefilterFrontEnd.capture_output(self)
272
284
273
285
274 def release_output(self):
286 def release_output(self):
275 __builtin__.raw_input = self.__old_raw_input
287 __builtin__.raw_input = self.__old_raw_input
276 PrefilterFrontEnd.capture_output(self)
288 PrefilterFrontEnd.release_output(self)
277
289
278
290
279 def after_execute(self):
291 def after_execute(self):
280 PrefilterFrontEnd.after_execute(self)
292 PrefilterFrontEnd.after_execute(self)
281 # Clear the wait cursor
293 # Clear the wait cursor
282 if hasattr(self, '_cursor'):
294 if hasattr(self, '_cursor'):
283 del self._cursor
295 del self._cursor
284
296
285
297
286 def show_traceback(self):
298 def show_traceback(self):
287 start_line = self.GetCurrentLine()
299 start_line = self.GetCurrentLine()
288 PrefilterFrontEnd.show_traceback(self)
300 PrefilterFrontEnd.show_traceback(self)
289 wx.Yield()
301 wx.Yield()
290 for i in range(start_line, self.GetCurrentLine()):
302 for i in range(start_line, self.GetCurrentLine()):
291 self.MarkerAdd(i, _ERROR_MARKER)
303 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
292
304
293
305
294 #--------------------------------------------------------------------------
306 #--------------------------------------------------------------------------
295 # ConsoleWidget interface
307 # ConsoleWidget interface
296 #--------------------------------------------------------------------------
308 #--------------------------------------------------------------------------
297
309
298 def new_prompt(self, prompt):
310 def new_prompt(self, prompt):
299 """ Display a new prompt, and start a new input buffer.
311 """ Display a new prompt, and start a new input buffer.
300 """
312 """
301 self._input_state = 'readline'
313 self._input_state = 'readline'
302 ConsoleWidget.new_prompt(self, prompt)
314 ConsoleWidget.new_prompt(self, prompt)
303
315
304
316
305 def write(self, *args, **kwargs):
317 def write(self, *args, **kwargs):
306 # Avoid multiple inheritence, be explicit about which
318 # Avoid multiple inheritence, be explicit about which
307 # parent method class gets called
319 # parent method class gets called
308 ConsoleWidget.write(self, *args, **kwargs)
320 ConsoleWidget.write(self, *args, **kwargs)
309
321
310
322
311 def _on_key_down(self, event, skip=True):
323 def _on_key_down(self, event, skip=True):
312 """ Capture the character events, let the parent
324 """ Capture the character events, let the parent
313 widget handle them, and put our logic afterward.
325 widget handle them, and put our logic afterward.
314 """
326 """
315 # FIXME: This method needs to be broken down in smaller ones.
327 # FIXME: This method needs to be broken down in smaller ones.
316 current_line_number = self.GetCurrentLine()
328 current_line_number = self.GetCurrentLine()
317 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
329 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
318 # Capture Control-C
330 # Capture Control-C
319 if self._input_state == 'subprocess':
331 if self._input_state == 'subprocess':
320 if self.debug:
332 if self.debug:
321 print >>sys.__stderr__, 'Killing running process'
333 print >>sys.__stderr__, 'Killing running process'
322 self._running_process.process.kill()
334 self._running_process.process.kill()
323 elif self._input_state == 'buffering':
335 elif self._input_state == 'buffering':
324 if self.debug:
336 if self.debug:
325 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
337 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
326 raise KeyboardInterrupt
338 raise KeyboardInterrupt
327 # XXX: We need to make really sure we
339 # XXX: We need to make really sure we
328 # get back to a prompt.
340 # get back to a prompt.
329 elif self._input_state == 'subprocess' and (
341 elif self._input_state == 'subprocess' and (
330 ( event.KeyCode<256 and
342 ( event.KeyCode<256 and
331 not event.ControlDown() )
343 not event.ControlDown() )
332 or
344 or
333 ( event.KeyCode in (ord('d'), ord('D')) and
345 ( event.KeyCode in (ord('d'), ord('D')) and
334 event.ControlDown())):
346 event.ControlDown())):
335 # We are running a process, we redirect keys.
347 # We are running a process, we redirect keys.
336 ConsoleWidget._on_key_down(self, event, skip=skip)
348 ConsoleWidget._on_key_down(self, event, skip=skip)
337 char = chr(event.KeyCode)
349 char = chr(event.KeyCode)
338 # Deal with some inconsistency in wx keycodes:
350 # Deal with some inconsistency in wx keycodes:
339 if char == '\r':
351 if char == '\r':
340 char = '\n'
352 char = '\n'
341 elif not event.ShiftDown():
353 elif not event.ShiftDown():
342 char = char.lower()
354 char = char.lower()
343 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
355 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
344 char = '\04'
356 char = '\04'
345 self._running_process.process.stdin.write(char)
357 self._running_process.process.stdin.write(char)
346 self._running_process.process.stdin.flush()
358 self._running_process.process.stdin.flush()
347 elif event.KeyCode in (ord('('), 57):
359 elif event.KeyCode in (ord('('), 57):
348 # Calltips
360 # Calltips
349 event.Skip()
361 event.Skip()
350 self.do_calltip()
362 self.do_calltip()
351 elif self.AutoCompActive():
363 elif self.AutoCompActive():
352 event.Skip()
364 event.Skip()
353 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
365 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
354 wx.CallAfter(self._popup_completion, create=True)
366 wx.CallAfter(self._popup_completion, create=True)
355 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
367 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
356 wx.WXK_RIGHT):
368 wx.WXK_RIGHT):
357 wx.CallAfter(self._popup_completion)
369 wx.CallAfter(self._popup_completion)
358 else:
370 else:
359 # Up history
371 # Up history
360 if event.KeyCode == wx.WXK_UP and (
372 if event.KeyCode == wx.WXK_UP and (
361 ( current_line_number == self.current_prompt_line and
373 ( current_line_number == self.current_prompt_line and
362 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
374 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
363 or event.ControlDown() ):
375 or event.ControlDown() ):
364 new_buffer = self.get_history_previous(
376 new_buffer = self.get_history_previous(
365 self.input_buffer)
377 self.input_buffer)
366 if new_buffer is not None:
378 if new_buffer is not None:
367 self.input_buffer = new_buffer
379 self.input_buffer = new_buffer
368 if self.GetCurrentLine() > self.current_prompt_line:
380 if self.GetCurrentLine() > self.current_prompt_line:
369 # Go to first line, for seemless history up.
381 # Go to first line, for seemless history up.
370 self.GotoPos(self.current_prompt_pos)
382 self.GotoPos(self.current_prompt_pos)
371 # Down history
383 # Down history
372 elif event.KeyCode == wx.WXK_DOWN and (
384 elif event.KeyCode == wx.WXK_DOWN and (
373 ( current_line_number == self.LineCount -1 and
385 ( current_line_number == self.LineCount -1 and
374 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
386 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
375 or event.ControlDown() ):
387 or event.ControlDown() ):
376 new_buffer = self.get_history_next()
388 new_buffer = self.get_history_next()
377 if new_buffer is not None:
389 if new_buffer is not None:
378 self.input_buffer = new_buffer
390 self.input_buffer = new_buffer
379 # Tab-completion
391 # Tab-completion
380 elif event.KeyCode == ord('\t'):
392 elif event.KeyCode == ord('\t'):
381 last_line = self.input_buffer.split('\n')[-1]
393 last_line = self.input_buffer.split('\n')[-1]
382 if not re.match(r'^\s*$', last_line):
394 if not re.match(r'^\s*$', last_line):
383 self.complete_current_input()
395 self.complete_current_input()
384 else:
396 else:
385 event.Skip()
397 event.Skip()
386 else:
398 else:
387 ConsoleWidget._on_key_down(self, event, skip=skip)
399 ConsoleWidget._on_key_down(self, event, skip=skip)
388
400
389
401
390 def _on_key_up(self, event, skip=True):
402 def _on_key_up(self, event, skip=True):
391 """ Called when any key is released.
403 """ Called when any key is released.
392 """
404 """
393 if event.KeyCode in (59, ord('.')):
405 if event.KeyCode in (59, ord('.')):
394 # Intercepting '.'
406 # Intercepting '.'
395 event.Skip()
407 event.Skip()
396 self._popup_completion(create=True)
408 self._popup_completion(create=True)
397 else:
409 else:
398 ConsoleWidget._on_key_up(self, event, skip=skip)
410 ConsoleWidget._on_key_up(self, event, skip=skip)
399
411
400
412
401 def _on_enter(self):
413 def _on_enter(self):
402 """ Called on return key down, in readline input_state.
414 """ Called on return key down, in readline input_state.
403 """
415 """
404 if self.debug:
416 if self.debug:
405 print >>sys.__stdout__, repr(self.input_buffer)
417 print >>sys.__stdout__, repr(self.input_buffer)
418 i = self.GetLineCount()
419 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
406 PrefilterFrontEnd._on_enter(self)
420 PrefilterFrontEnd._on_enter(self)
407
421
408
422
409 #--------------------------------------------------------------------------
423 #--------------------------------------------------------------------------
410 # Private API
424 # Private API
411 #--------------------------------------------------------------------------
425 #--------------------------------------------------------------------------
412
426
413 def _end_system_call(self):
427 def _end_system_call(self):
414 """ Called at the end of a system call.
428 """ Called at the end of a system call.
415 """
429 """
416 self._input_state = 'buffering'
430 self._input_state = 'buffering'
417 self._running_process = False
431 self._running_process = False
418
432
419
433
420 def _buffer_flush(self, event):
434 def _buffer_flush(self, event):
421 """ Called by the timer to flush the write buffer.
435 """ Called by the timer to flush the write buffer.
422
436
423 This is always called in the mainloop, by the wx timer.
437 This is always called in the mainloop, by the wx timer.
424 """
438 """
425 self._out_buffer_lock.acquire()
439 self._out_buffer_lock.acquire()
426 _out_buffer = self._out_buffer
440 _out_buffer = self._out_buffer
427 self._out_buffer = []
441 self._out_buffer = []
428 self._out_buffer_lock.release()
442 self._out_buffer_lock.release()
429 self.write(''.join(_out_buffer), refresh=False)
443 self.write(''.join(_out_buffer), refresh=False)
430 self._buffer_flush_timer.Stop()
444 self._buffer_flush_timer.Stop()
431
445
432
446
433 if __name__ == '__main__':
447 if __name__ == '__main__':
434 class MainWindow(wx.Frame):
448 class MainWindow(wx.Frame):
435 def __init__(self, parent, id, title):
449 def __init__(self, parent, id, title):
436 wx.Frame.__init__(self, parent, id, title, size=(300,250))
450 wx.Frame.__init__(self, parent, id, title, size=(300,250))
437 self._sizer = wx.BoxSizer(wx.VERTICAL)
451 self._sizer = wx.BoxSizer(wx.VERTICAL)
438 self.shell = WxController(self)
452 self.shell = WxController(self)
439 self._sizer.Add(self.shell, 1, wx.EXPAND)
453 self._sizer.Add(self.shell, 1, wx.EXPAND)
440 self.SetSizer(self._sizer)
454 self.SetSizer(self._sizer)
441 self.SetAutoLayout(1)
455 self.SetAutoLayout(1)
442 self.Show(True)
456 self.Show(True)
443
457
444 app = wx.PySimpleApp()
458 app = wx.PySimpleApp()
445 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
459 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
446 frame.shell.SetFocus()
460 frame.shell.SetFocus()
447 frame.SetSize((680, 460))
461 frame.SetSize((680, 460))
448 self = frame.shell
462 self = frame.shell
449
463
450 app.MainLoop()
464 app.MainLoop()
451
465
@@ -1,105 +1,107 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """ Trap stdout/stderr."""
3 """ Trap stdout/stderr."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 import sys
18 import sys
19 from cStringIO import StringIO
19 from cStringIO import StringIO
20
20
21
21
22 class OutputTrap(object):
22 class OutputTrap(object):
23 """ Object which can trap text sent to stdout and stderr.
23 """ Object which can trap text sent to stdout and stderr.
24 """
24 """
25
25
26 def __init__(self, out=None, err=None):
26 def __init__(self, out=None, err=None):
27 # Filelike objects to store stdout/stderr text.
27 # Filelike objects to store stdout/stderr text.
28 if out is None:
28 if out is None:
29 self.out = StringIO()
29 self.out = StringIO()
30 else:
30 else:
31 self.out = out
31 self.out = out
32 if err is None:
32 if err is None:
33 self.err = StringIO()
33 self.err = StringIO()
34 else:
34 else:
35 self.err = err
35 self.err = err
36
36
37 # Boolean to check if the stdout/stderr hook is set.
37 # Boolean to check if the stdout/stderr hook is set.
38 self.out_set = False
38 self.out_set = False
39 self.err_set = False
39 self.err_set = False
40
40
41 @property
41 @property
42 def out_text(self):
42 def out_text(self):
43 """ Return the text currently in the stdout buffer.
43 """ Return the text currently in the stdout buffer.
44 """
44 """
45 return self.out.getvalue()
45 return self.out.getvalue()
46
46
47 @property
47 @property
48 def err_text(self):
48 def err_text(self):
49 """ Return the text currently in the stderr buffer.
49 """ Return the text currently in the stderr buffer.
50 """
50 """
51 return self.err.getvalue()
51 return self.err.getvalue()
52
52
53 def set(self):
53 def set(self):
54 """ Set the hooks.
54 """ Set the hooks.
55 """
55 """
56
56
57 if sys.stdout is not self.out:
57 if sys.stdout is not self.out:
58 self._out_save = sys.stdout
58 self._out_save = sys.stdout
59 sys.stdout = self.out
59 sys.stdout = self.out
60 self.out_set = True
60 self.out_set = True
61
61
62 if sys.stderr is not self.err:
62 if sys.stderr is not self.err:
63 self._err_save = sys.stderr
63 self._err_save = sys.stderr
64 sys.stderr = self.err
64 sys.stderr = self.err
65 self.err_set = True
65 self.err_set = True
66
66
67 def unset(self):
67 def unset(self):
68 """ Remove the hooks.
68 """ Remove the hooks.
69 """
69 """
70
70
71 sys.stdout = self._out_save
71 if self.out_set:
72 sys.stdout = self._out_save
72 self.out_set = False
73 self.out_set = False
73
74
74 sys.stderr = self._err_save
75 if self.err_set:
76 sys.stderr = self._err_save
75 self.err_set = False
77 self.err_set = False
76
78
77 def clear(self):
79 def clear(self):
78 """ Clear out the buffers.
80 """ Clear out the buffers.
79 """
81 """
80
82
81 self.out.reset()
83 self.out.reset()
82 self.out.truncate()
84 self.out.truncate()
83
85
84 self.err.reset()
86 self.err.reset()
85 self.err.truncate()
87 self.err.truncate()
86
88
87 def add_to_message(self, message):
89 def add_to_message(self, message):
88 """ Add the text from stdout and stderr to the message from the
90 """ Add the text from stdout and stderr to the message from the
89 interpreter to its listeners.
91 interpreter to its listeners.
90
92
91 Parameters
93 Parameters
92 ----------
94 ----------
93 message : dict
95 message : dict
94 """
96 """
95
97
96 out_text = self.out_text
98 out_text = self.out_text
97 if out_text:
99 if out_text:
98 message['stdout'] = out_text
100 message['stdout'] = out_text
99
101
100 err_text = self.err_text
102 err_text = self.err_text
101 if err_text:
103 if err_text:
102 message['stderr'] = err_text
104 message['stderr'] = err_text
103
105
104
106
105
107
General Comments 0
You need to be logged in to leave comments. Login now