##// END OF EJS Templates
More code reuse between GUI-independant frontend and Wx frontend: getting...
gvaroquaux -
Show More
@@ -1,239 +1,231 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 IPython
21 import IPython
22 import sys
22 import sys
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 input buffer being edited
60 input_buffer = ''
61
59 #--------------------------------------------------------------------------
62 #--------------------------------------------------------------------------
60 # FrontEndBase interface
63 # FrontEndBase interface
61 #--------------------------------------------------------------------------
64 #--------------------------------------------------------------------------
62
65
63 def __init__(self, shell=None, history=None):
66 def __init__(self, shell=None, history=None):
64 if shell is None:
67 if shell is None:
65 shell = Interpreter()
68 shell = Interpreter()
66 FrontEndBase.__init__(self, shell=shell, history=history)
69 FrontEndBase.__init__(self, shell=shell, history=history)
67
70
68 self.new_prompt(self.input_prompt_template.substitute(number=1))
71 self.new_prompt(self.input_prompt_template.substitute(number=1))
69
72
70
73
71 def complete(self, line):
74 def complete(self, line):
72 """Complete line in engine's user_ns
75 """Complete line in engine's user_ns
73
76
74 Parameters
77 Parameters
75 ----------
78 ----------
76 line : string
79 line : string
77
80
78 Result
81 Result
79 ------
82 ------
80 The replacement for the line and the list of possible completions.
83 The replacement for the line and the list of possible completions.
81 """
84 """
82 completions = self.shell.complete(line)
85 completions = self.shell.complete(line)
83 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
86 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
84 if completions:
87 if completions:
85 prefix = common_prefix(completions)
88 prefix = common_prefix(completions)
86 residual = complete_sep.split(line)[:-1]
89 residual = complete_sep.split(line)[:-1]
87 line = line[:-len(residual)] + prefix
90 line = line[:-len(residual)] + prefix
88 return line, completions
91 return line, completions
89
92
90
93
91 def render_result(self, result):
94 def render_result(self, result):
92 """ Frontend-specific rendering of the result of a calculation
95 """ Frontend-specific rendering of the result of a calculation
93 that has been sent to an engine.
96 that has been sent to an engine.
94 """
97 """
95 if 'stdout' in result and result['stdout']:
98 if 'stdout' in result and result['stdout']:
96 self.write('\n' + result['stdout'])
99 self.write('\n' + result['stdout'])
97 if 'display' in result and result['display']:
100 if 'display' in result and result['display']:
98 self.write("%s%s\n" % (
101 self.write("%s%s\n" % (
99 self.output_prompt_template.substitute(
102 self.output_prompt_template.substitute(
100 number=result['number']),
103 number=result['number']),
101 result['display']['pprint']
104 result['display']['pprint']
102 ) )
105 ) )
103
106
104
107
105 def render_error(self, failure):
108 def render_error(self, failure):
106 """ Frontend-specific rendering of error.
109 """ Frontend-specific rendering of error.
107 """
110 """
108 self.write('\n\n'+str(failure)+'\n\n')
111 self.write('\n\n'+str(failure)+'\n\n')
109 return failure
112 return failure
110
113
111
114
112 def is_complete(self, string):
115 def is_complete(self, string):
113 """ Check if a string forms a complete, executable set of
116 """ Check if a string forms a complete, executable set of
114 commands.
117 commands.
115
118
116 For the line-oriented frontend, multi-line code is not executed
119 For the line-oriented frontend, multi-line code is not executed
117 as soon as it is complete: the users has to enter two line
120 as soon as it is complete: the users has to enter two line
118 returns.
121 returns.
119 """
122 """
120 if string in ('', '\n'):
123 if string in ('', '\n'):
121 # Prefiltering, eg through ipython0, may return an empty
124 # Prefiltering, eg through ipython0, may return an empty
122 # string although some operations have been accomplished. We
125 # string although some operations have been accomplished. We
123 # thus want to consider an empty string as a complete
126 # thus want to consider an empty string as a complete
124 # statement.
127 # statement.
125 return True
128 return True
126 elif ( len(self.get_current_edit_buffer().split('\n'))>2
129 elif ( len(self.input_buffer.split('\n'))>2
127 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
130 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
128 return False
131 return False
129 else:
132 else:
130 # Add line returns here, to make sure that the statement is
133 # Add line returns here, to make sure that the statement is
131 # complete.
134 # complete.
132 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
135 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
133
136
134
137
135 def get_current_edit_buffer(self):
136 """ Return the current buffer being entered.
137 """
138 raise NotImplementedError
139
140
141 def write(self, string):
138 def write(self, string):
142 """ Write some characters to the display.
139 """ Write some characters to the display.
143
140
144 Subclass should overide this method.
141 Subclass should overide this method.
145 """
142 """
146 print >>sys.__stderr__, string
143 print >>sys.__stderr__, string
147
144
148
145
149 def add_to_edit_buffer(self, string):
150 """ Add the given string to the current edit buffer.
151 """
152 raise NotImplementedError
153
154
155 def new_prompt(self, prompt):
146 def new_prompt(self, prompt):
156 """ Prints a prompt and starts a new editing buffer.
147 """ Prints a prompt and starts a new editing buffer.
157
148
158 Subclasses should use this method to make sure that the
149 Subclasses should use this method to make sure that the
159 terminal is put in a state favorable for a new line
150 terminal is put in a state favorable for a new line
160 input.
151 input.
161 """
152 """
153 self.input_buffer = ''
162 self.write(prompt)
154 self.write(prompt)
163
155
164
156
165 def execute(self, python_string, raw_string=None):
157 def execute(self, python_string, raw_string=None):
166 """ Stores the raw_string in the history, and sends the
158 """ Stores the raw_string in the history, and sends the
167 python string to the interpreter.
159 python string to the interpreter.
168 """
160 """
169 if raw_string is None:
161 if raw_string is None:
170 raw_string = python_string
162 raw_string = python_string
171 # Create a false result, in case there is an exception
163 # Create a false result, in case there is an exception
172 self.last_result = dict(number=self.prompt_number)
164 self.last_result = dict(number=self.prompt_number)
173 try:
165 try:
174 self.history.input_cache[-1] = raw_string.rstrip()
166 self.history.input_cache[-1] = raw_string.rstrip()
175 result = self.shell.execute(python_string)
167 result = self.shell.execute(python_string)
176 self.last_result = result
168 self.last_result = result
177 self.render_result(result)
169 self.render_result(result)
178 except:
170 except:
179 self.show_traceback()
171 self.show_traceback()
180 finally:
172 finally:
181 self.after_execute()
173 self.after_execute()
182
174
183 #--------------------------------------------------------------------------
175 #--------------------------------------------------------------------------
184 # LineFrontEndBase interface
176 # LineFrontEndBase interface
185 #--------------------------------------------------------------------------
177 #--------------------------------------------------------------------------
186
178
187 def prefilter_input(self, string):
179 def prefilter_input(self, string):
188 """ Priflter the input to turn it in valid python.
180 """ Priflter the input to turn it in valid python.
189 """
181 """
190 string = string.replace('\r\n', '\n')
182 string = string.replace('\r\n', '\n')
191 string = string.replace('\t', 4*' ')
183 string = string.replace('\t', 4*' ')
192 # Clean the trailing whitespace
184 # Clean the trailing whitespace
193 string = '\n'.join(l.rstrip() for l in string.split('\n'))
185 string = '\n'.join(l.rstrip() for l in string.split('\n'))
194 return string
186 return string
195
187
196 def after_execute(self):
188 def after_execute(self):
197 """ All the operations required after an execution to put the
189 """ All the operations required after an execution to put the
198 terminal back in a shape where it is usable.
190 terminal back in a shape where it is usable.
199 """
191 """
200 self.prompt_number += 1
192 self.prompt_number += 1
201 self.new_prompt(self.input_prompt_template.substitute(
193 self.new_prompt(self.input_prompt_template.substitute(
202 number=(self.last_result['number'] + 1)))
194 number=(self.last_result['number'] + 1)))
203 # Start a new empty history entry
195 # Start a new empty history entry
204 self._add_history(None, '')
196 self._add_history(None, '')
205 self.history_cursor = len(self.history.input_cache) - 1
197 self.history_cursor = len(self.history.input_cache) - 1
206
198
207
199
208 #--------------------------------------------------------------------------
200 #--------------------------------------------------------------------------
209 # Private API
201 # Private API
210 #--------------------------------------------------------------------------
202 #--------------------------------------------------------------------------
211
203
212 def _on_enter(self):
204 def _on_enter(self):
213 """ Called when the return key is pressed in a line editing
205 """ Called when the return key is pressed in a line editing
214 buffer.
206 buffer.
215 """
207 """
216 current_buffer = self.get_current_edit_buffer()
208 current_buffer = self.input_buffer
217 cleaned_buffer = self.prefilter_input(current_buffer)
209 cleaned_buffer = self.prefilter_input(current_buffer)
218 if self.is_complete(cleaned_buffer):
210 if self.is_complete(cleaned_buffer):
219 self.execute(cleaned_buffer, raw_string=current_buffer)
211 self.execute(cleaned_buffer, raw_string=current_buffer)
220 else:
212 else:
221 self.add_to_edit_buffer(self._get_indent_string(
213 self.input_buffer += self._get_indent_string(
222 current_buffer[:-1]))
214 current_buffer[:-1])
223 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
215 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
224 self.add_to_edit_buffer('\t')
216 self.input_buffer += '\t'
225
217
226
218
227 def _get_indent_string(self, string):
219 def _get_indent_string(self, string):
228 """ Return the string of whitespace that prefixes a line. Used to
220 """ Return the string of whitespace that prefixes a line. Used to
229 add the right amount of indendation when creating a new line.
221 add the right amount of indendation when creating a new line.
230 """
222 """
231 string = string.replace('\t', ' '*4)
223 string = string.replace('\t', ' '*4)
232 string = string.split('\n')[-1]
224 string = string.split('\n')[-1]
233 indent_chars = len(string) - len(string.lstrip())
225 indent_chars = len(string) - len(string.lstrip())
234 indent_string = '\t'*(indent_chars // 4) + \
226 indent_string = '\t'*(indent_chars // 4) + \
235 ' '*(indent_chars % 4)
227 ' '*(indent_chars % 4)
236
228
237 return indent_string
229 return indent_string
238
230
239
231
@@ -1,135 +1,125 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
18
19 class TestPrefilterFrontEnd(PrefilterFrontEnd):
19 class TestPrefilterFrontEnd(PrefilterFrontEnd):
20
20
21 input_prompt_template = string.Template('')
21 input_prompt_template = string.Template('')
22 output_prompt_template = string.Template('')
22 output_prompt_template = string.Template('')
23
23
24 edit_buffer = ''
25
26
27 def __init__(self):
24 def __init__(self):
28 self.out = StringIO()
25 self.out = StringIO()
29 PrefilterFrontEnd.__init__(self)
26 PrefilterFrontEnd.__init__(self)
30
27
31 def get_current_edit_buffer(self):
32 return self.edit_buffer
33
34 def add_to_edit_buffer(self, string):
35 self.edit_buffer += string
36
37 def write(self, string):
28 def write(self, string):
38 self.out.write(string)
29 self.out.write(string)
39
30
40 def _on_enter(self):
31 def _on_enter(self):
41 self.add_to_edit_buffer('\n')
32 self.input_buffer += '\n'
42 PrefilterFrontEnd._on_enter(self)
33 PrefilterFrontEnd._on_enter(self)
43
34
44 def new_prompt(self, prompt):
45 self.edit_buffer = ''
46 PrefilterFrontEnd.new_prompt(self, prompt)
47
48
35
49 def test_execution():
36 def test_execution():
50 """ Test execution of a command.
37 """ Test execution of a command.
51 """
38 """
52 f = TestPrefilterFrontEnd()
39 f = TestPrefilterFrontEnd()
53 f.edit_buffer='print 1\n'
40 f.input_buffer = 'print 1\n'
54 f._on_enter()
41 f._on_enter()
55 assert f.out.getvalue() == '1\n'
42 assert f.out.getvalue() == '1\n'
56
43
57
44
58 def test_multiline():
45 def test_multiline():
59 """ Test execution of a multiline command.
46 """ Test execution of a multiline command.
60 """
47 """
61 f = TestPrefilterFrontEnd()
48 f = TestPrefilterFrontEnd()
62 f.edit_buffer='if True:'
49 f.input_buffer = 'if True:'
63 f._on_enter()
50 f._on_enter()
64 f.add_to_edit_buffer('print 1')
51 f.input_buffer += 'print 1'
65 f._on_enter()
52 f._on_enter()
66 assert f.out.getvalue() == ''
53 assert f.out.getvalue() == ''
67 f._on_enter()
54 f._on_enter()
68 assert f.out.getvalue() == '1\n'
55 assert f.out.getvalue() == '1\n'
69 f = TestPrefilterFrontEnd()
56 f = TestPrefilterFrontEnd()
70 f.edit_buffer='(1 +'
57 f.input_buffer='(1 +'
71 f._on_enter()
58 f._on_enter()
72 f.add_to_edit_buffer('0)')
59 f.input_buffer += '0)'
73 f._on_enter()
60 f._on_enter()
74 assert f.out.getvalue() == ''
61 assert f.out.getvalue() == ''
75 f._on_enter()
62 f._on_enter()
76 assert f.out.getvalue() == '1\n'
63 assert f.out.getvalue() == '1\n'
77
64
78
65
79 def test_capture():
66 def test_capture():
80 """ Test the capture of output in different channels.
67 """ Test the capture of output in different channels.
81 """
68 """
82 # Test on the OS-level stdout, stderr.
69 # Test on the OS-level stdout, stderr.
83 f = TestPrefilterFrontEnd()
70 f = TestPrefilterFrontEnd()
84 f.edit_buffer='import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
71 f.input_buffer = \
72 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
85 f._on_enter()
73 f._on_enter()
86 assert f.out.getvalue() == '1'
74 assert f.out.getvalue() == '1'
87 f = TestPrefilterFrontEnd()
75 f = TestPrefilterFrontEnd()
88 f.edit_buffer='import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
76 f.input_buffer = \
77 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
89 f._on_enter()
78 f._on_enter()
90 assert f.out.getvalue() == '1'
79 assert f.out.getvalue() == '1'
91
80
92
81
93 def test_magic():
82 def test_magic():
94 """ Test the magic expansion and history.
83 """ Test the magic expansion and history.
95
84
96 This test is fairly fragile and will break when magics change.
85 This test is fairly fragile and will break when magics change.
97 """
86 """
98 f = TestPrefilterFrontEnd()
87 f = TestPrefilterFrontEnd()
99 f.add_to_edit_buffer('%who\n')
88 f.input_buffer += '%who\n'
100 f._on_enter()
89 f._on_enter()
101 assert f.out.getvalue() == 'Interactive namespace is empty.\n'
90 assert f.out.getvalue() == 'Interactive namespace is empty.\n'
102
91
103
92
104 def test_help():
93 def test_help():
105 """ Test object inspection.
94 """ Test object inspection.
106 """
95 """
107 f = TestPrefilterFrontEnd()
96 f = TestPrefilterFrontEnd()
108 f.add_to_edit_buffer("def f():")
97 f.input_buffer += "def f():"
109 f._on_enter()
98 f._on_enter()
110 f.add_to_edit_buffer("'foobar'")
99 f.input_buffer += "'foobar'"
111 f._on_enter()
100 f._on_enter()
112 f.add_to_edit_buffer("pass")
101 f.input_buffer += "pass"
113 f._on_enter()
102 f._on_enter()
114 f._on_enter()
103 f._on_enter()
115 f.add_to_edit_buffer("f?")
104 f.input_buffer += "f?"
116 f._on_enter()
105 f._on_enter()
117 assert f.out.getvalue().split()[-1] == 'foobar'
106 assert f.out.getvalue().split()[-1] == 'foobar'
118
107
108
119 def test_completion():
109 def test_completion():
120 """ Test command-line completion.
110 """ Test command-line completion.
121 """
111 """
122 f = TestPrefilterFrontEnd()
112 f = TestPrefilterFrontEnd()
123 f.edit_buffer = 'zzza = 1'
113 f.input_buffer = 'zzza = 1'
124 f._on_enter()
114 f._on_enter()
125 f.edit_buffer = 'zzzb = 2'
115 f.input_buffer = 'zzzb = 2'
126 f._on_enter()
116 f._on_enter()
127 f.edit_buffer = 'zz'
117 f.input_buffer = 'zz'
128
118
129
119
130 if __name__ == '__main__':
120 if __name__ == '__main__':
131 test_magic()
121 test_magic()
132 test_help()
122 test_help()
133 test_execution()
123 test_execution()
134 test_multiline()
124 test_multiline()
135 test_capture()
125 test_capture()
@@ -1,439 +1,437 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.
84 def _set_input_buffer(self, string):
85 self.SetSelection(self.current_prompt_pos, self.GetLength())
86 self.ReplaceSelection(string)
87 self.GotoPos(self.GetLength())
88
89 def _get_input_buffer(self):
90 """ Returns the text in current edit buffer.
91 """
92 input_buffer = self.GetTextRange(self.current_prompt_pos,
93 self.GetLength())
94 input_buffer = input_buffer.replace(LINESEP, '\n')
95 return input_buffer
96
97 input_buffer = property(_get_input_buffer, _set_input_buffer)
98
83 style = _DEFAULT_STYLE.copy()
99 style = _DEFAULT_STYLE.copy()
84
100
85 # Translation table from ANSI escape sequences to color. Override
101 # Translation table from ANSI escape sequences to color. Override
86 # this to specify your colors.
102 # this to specify your colors.
87 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
103 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
88 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
104 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
89 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
105 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
90 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
106 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
91 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
107 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
92 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
108 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
93 '1;34': [12, 'LIGHT BLUE'], '1;35':
109 '1;34': [12, 'LIGHT BLUE'], '1;35':
94 [13, 'MEDIUM VIOLET RED'],
110 [13, 'MEDIUM VIOLET RED'],
95 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
111 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
96
112
97 # The color of the carret (call _apply_style() after setting)
113 # The color of the carret (call _apply_style() after setting)
98 carret_color = 'BLACK'
114 carret_color = 'BLACK'
99
115
100 #--------------------------------------------------------------------------
116 #--------------------------------------------------------------------------
101 # Public API
117 # Public API
102 #--------------------------------------------------------------------------
118 #--------------------------------------------------------------------------
103
119
104 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
120 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
105 size=wx.DefaultSize, style=0, ):
121 size=wx.DefaultSize, style=0, ):
106 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
122 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
107 self._configure_scintilla()
123 self._configure_scintilla()
108
124
109 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
125 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
110 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
126 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
111
127
112
128
113 def write(self, text, refresh=True):
129 def write(self, text, refresh=True):
114 """ Write given text to buffer, while translating the ansi escape
130 """ Write given text to buffer, while translating the ansi escape
115 sequences.
131 sequences.
116 """
132 """
117 # 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
118 # this method, the print statements will call this method, as
134 # this method, the print statements will call this method, as
119 # you will end up with an infinit loop
135 # you will end up with an infinit loop
120 if self.debug:
136 if self.debug:
121 print >>sys.__stderr__, text
137 print >>sys.__stderr__, text
122 title = self.title_pat.split(text)
138 title = self.title_pat.split(text)
123 if len(title)>1:
139 if len(title)>1:
124 self.title = title[-2]
140 self.title = title[-2]
125
141
126 text = self.title_pat.sub('', text)
142 text = self.title_pat.sub('', text)
127 segments = self.color_pat.split(text)
143 segments = self.color_pat.split(text)
128 segment = segments.pop(0)
144 segment = segments.pop(0)
129 self.GotoPos(self.GetLength())
145 self.GotoPos(self.GetLength())
130 self.StartStyling(self.GetLength(), 0xFF)
146 self.StartStyling(self.GetLength(), 0xFF)
131 try:
147 try:
132 self.AppendText(segment)
148 self.AppendText(segment)
133 except UnicodeDecodeError:
149 except UnicodeDecodeError:
134 # XXX: Do I really want to skip the exception?
150 # XXX: Do I really want to skip the exception?
135 pass
151 pass
136
152
137 if segments:
153 if segments:
138 for ansi_tag, text in zip(segments[::2], segments[1::2]):
154 for ansi_tag, text in zip(segments[::2], segments[1::2]):
139 self.StartStyling(self.GetLength(), 0xFF)
155 self.StartStyling(self.GetLength(), 0xFF)
140 try:
156 try:
141 self.AppendText(text)
157 self.AppendText(text)
142 except UnicodeDecodeError:
158 except UnicodeDecodeError:
143 # XXX: Do I really want to skip the exception?
159 # XXX: Do I really want to skip the exception?
144 pass
160 pass
145
161
146 if ansi_tag not in self.ANSI_STYLES:
162 if ansi_tag not in self.ANSI_STYLES:
147 style = 0
163 style = 0
148 else:
164 else:
149 style = self.ANSI_STYLES[ansi_tag][0]
165 style = self.ANSI_STYLES[ansi_tag][0]
150
166
151 self.SetStyling(len(text), style)
167 self.SetStyling(len(text), style)
152
168
153 self.GotoPos(self.GetLength())
169 self.GotoPos(self.GetLength())
154 if refresh:
170 if refresh:
155 wx.Yield()
171 wx.Yield()
156
172
157
173
158 def new_prompt(self, prompt):
174 def new_prompt(self, prompt):
159 """ Prints a prompt at start of line, and move the start of the
175 """ Prints a prompt at start of line, and move the start of the
160 current block there.
176 current block there.
161
177
162 The prompt can be give with ascii escape sequences.
178 The prompt can be give with ascii escape sequences.
163 """
179 """
164 self.write(prompt)
180 self.write(prompt)
165 # now we update our cursor giving end of prompt
181 # now we update our cursor giving end of prompt
166 self.current_prompt_pos = self.GetLength()
182 self.current_prompt_pos = self.GetLength()
167 self.current_prompt_line = self.GetCurrentLine()
183 self.current_prompt_line = self.GetCurrentLine()
168 wx.Yield()
184 wx.Yield()
169 self.EnsureCaretVisible()
185 self.EnsureCaretVisible()
170
186
171
187
172 def replace_current_edit_buffer(self, text):
173 """ Replace currently entered command line with given text.
174 """
175 self.SetSelection(self.current_prompt_pos, self.GetLength())
176 self.ReplaceSelection(text)
177 self.GotoPos(self.GetLength())
178
179
180 def get_current_edit_buffer(self):
181 """ Returns the text in current edit buffer.
182 """
183 current_edit_buffer = self.GetTextRange(self.current_prompt_pos,
184 self.GetLength())
185 current_edit_buffer = current_edit_buffer.replace(LINESEP, '\n')
186 return current_edit_buffer
187
188
189 def scroll_to_bottom(self):
188 def scroll_to_bottom(self):
190 maxrange = self.GetScrollRange(wx.VERTICAL)
189 maxrange = self.GetScrollRange(wx.VERTICAL)
191 self.ScrollLines(maxrange)
190 self.ScrollLines(maxrange)
192
191
193
192
194 def pop_completion(self, possibilities, offset=0):
193 def pop_completion(self, possibilities, offset=0):
195 """ Pops up an autocompletion menu. Offset is the offset
194 """ Pops up an autocompletion menu. Offset is the offset
196 in characters of the position at which the menu should
195 in characters of the position at which the menu should
197 appear, relativ to the cursor.
196 appear, relativ to the cursor.
198 """
197 """
199 self.AutoCompSetIgnoreCase(False)
198 self.AutoCompSetIgnoreCase(False)
200 self.AutoCompSetAutoHide(False)
199 self.AutoCompSetAutoHide(False)
201 self.AutoCompSetMaxHeight(len(possibilities))
200 self.AutoCompSetMaxHeight(len(possibilities))
202 self.AutoCompShow(offset, " ".join(possibilities))
201 self.AutoCompShow(offset, " ".join(possibilities))
203
202
204
203
205 def write_completion(self, possibilities):
204 def write_completion(self, possibilities):
206 # FIXME: This is non Wx specific and needs to be moved into
205 # FIXME: This is non Wx specific and needs to be moved into
207 # the base class.
206 # the base class.
208 current_buffer = self.get_current_edit_buffer()
207 current_buffer = self.input_buffer
209
208
210 self.write('\n')
209 self.write('\n')
211 max_len = len(max(possibilities, key=len)) + 1
210 max_len = len(max(possibilities, key=len)) + 1
212
211
213 #now we check how much symbol we can put on a line...
212 #now we check how much symbol we can put on a line...
214 chars_per_line = self.GetSize()[0]/self.GetCharWidth()
213 chars_per_line = self.GetSize()[0]/self.GetCharWidth()
215 symbols_per_line = max(1, chars_per_line/max_len)
214 symbols_per_line = max(1, chars_per_line/max_len)
216
215
217 pos = 1
216 pos = 1
218 buf = []
217 buf = []
219 for symbol in possibilities:
218 for symbol in possibilities:
220 if pos < symbols_per_line:
219 if pos < symbols_per_line:
221 buf.append(symbol.ljust(max_len))
220 buf.append(symbol.ljust(max_len))
222 pos += 1
221 pos += 1
223 else:
222 else:
224 buf.append(symbol.rstrip() + '\n')
223 buf.append(symbol.rstrip() + '\n')
225 pos = 1
224 pos = 1
226 self.write(''.join(buf))
225 self.write(''.join(buf))
227 # FIXME: I have some mixing of interfaces between console_widget
226 # FIXME: I have some mixing of interfaces between console_widget
228 # and wx_frontend, here.
227 # and wx_frontend, here.
229 self.new_prompt(self.input_prompt_template.substitute(
228 self.new_prompt(self.input_prompt_template.substitute(
230 number=self.last_result['number'] + 1))
229 number=self.last_result['number'] + 1))
231 self.replace_current_edit_buffer(current_buffer)
230 self.input_buffer = current_buffer
232
231
233 #--------------------------------------------------------------------------
232 #--------------------------------------------------------------------------
234 # Private API
233 # Private API
235 #--------------------------------------------------------------------------
234 #--------------------------------------------------------------------------
236
235
237 def _apply_style(self):
236 def _apply_style(self):
238 """ Applies the colors for the different text elements and the
237 """ Applies the colors for the different text elements and the
239 carret.
238 carret.
240 """
239 """
241 self.SetCaretForeground(self.carret_color)
240 self.SetCaretForeground(self.carret_color)
242
241
243 #self.StyleClearAll()
242 #self.StyleClearAll()
244 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
243 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
245 "fore:#FF0000,back:#0000FF,bold")
244 "fore:#FF0000,back:#0000FF,bold")
246 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
245 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
247 "fore:#000000,back:#FF0000,bold")
246 "fore:#000000,back:#FF0000,bold")
248
247
249 for style in self.ANSI_STYLES.values():
248 for style in self.ANSI_STYLES.values():
250 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
249 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
251
250
252
251
253 def _configure_scintilla(self):
252 def _configure_scintilla(self):
254 self.SetEOLMode(stc.STC_EOL_LF)
253 self.SetEOLMode(stc.STC_EOL_LF)
255
254
256 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
255 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
257 # the widget
256 # the widget
258 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
257 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
259 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
258 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
260 # Also allow Ctrl Shift "=" for poor non US keyboard users.
259 # Also allow Ctrl Shift "=" for poor non US keyboard users.
261 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
260 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
262 stc.STC_CMD_ZOOMIN)
261 stc.STC_CMD_ZOOMIN)
263
262
264 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
263 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
265 # stc.STC_CMD_PAGEUP)
264 # stc.STC_CMD_PAGEUP)
266
265
267 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
266 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
268 # stc.STC_CMD_PAGEDOWN)
267 # stc.STC_CMD_PAGEDOWN)
269
268
270 # Keys: we need to clear some of the keys the that don't play
269 # Keys: we need to clear some of the keys the that don't play
271 # well with a console.
270 # well with a console.
272 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
271 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
273 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
272 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
274 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
273 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
275
274
276
275
277 self.SetEOLMode(stc.STC_EOL_CRLF)
276 self.SetEOLMode(stc.STC_EOL_CRLF)
278 self.SetWrapMode(stc.STC_WRAP_CHAR)
277 self.SetWrapMode(stc.STC_WRAP_CHAR)
279 self.SetWrapMode(stc.STC_WRAP_WORD)
278 self.SetWrapMode(stc.STC_WRAP_WORD)
280 self.SetBufferedDraw(True)
279 self.SetBufferedDraw(True)
281 self.SetUseAntiAliasing(True)
280 self.SetUseAntiAliasing(True)
282 self.SetLayoutCache(stc.STC_CACHE_PAGE)
281 self.SetLayoutCache(stc.STC_CACHE_PAGE)
283 self.SetUndoCollection(False)
282 self.SetUndoCollection(False)
284 self.SetUseTabs(True)
283 self.SetUseTabs(True)
285 self.SetIndent(4)
284 self.SetIndent(4)
286 self.SetTabWidth(4)
285 self.SetTabWidth(4)
287
286
288 self.EnsureCaretVisible()
287 self.EnsureCaretVisible()
289 # we don't want scintilla's autocompletion to choose
288 # we don't want scintilla's autocompletion to choose
290 # automaticaly out of a single choice list, as we pop it up
289 # automaticaly out of a single choice list, as we pop it up
291 # automaticaly
290 # automaticaly
292 self.AutoCompSetChooseSingle(False)
291 self.AutoCompSetChooseSingle(False)
293 self.AutoCompSetMaxHeight(10)
292 self.AutoCompSetMaxHeight(10)
294
293
295 self.SetMargins(3, 3) #text is moved away from border with 3px
294 self.SetMargins(3, 3) #text is moved away from border with 3px
296 # Suppressing Scintilla margins
295 # Suppressing Scintilla margins
297 self.SetMarginWidth(0, 0)
296 self.SetMarginWidth(0, 0)
298 self.SetMarginWidth(1, 0)
297 self.SetMarginWidth(1, 0)
299 self.SetMarginWidth(2, 0)
298 self.SetMarginWidth(2, 0)
300
299
301 self._apply_style()
300 self._apply_style()
302
301
303 # Xterm escape sequences
302 # Xterm escape sequences
304 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
303 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
305 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
304 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
306
305
307 #self.SetEdgeMode(stc.STC_EDGE_LINE)
306 #self.SetEdgeMode(stc.STC_EDGE_LINE)
308 #self.SetEdgeColumn(80)
307 #self.SetEdgeColumn(80)
309
308
310 # styles
309 # styles
311 p = self.style
310 p = self.style
312 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
311 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
313 self.StyleClearAll()
312 self.StyleClearAll()
314 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
313 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
315 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
314 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
316 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
315 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
317
316
318 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
317 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
319 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
318 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
320 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
319 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
321 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
320 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
322 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
321 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
323 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
322 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
324 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
323 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
325 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
324 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
326 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
325 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
327 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
326 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
328 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
327 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
329 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
328 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
330 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
329 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
331 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
330 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
332
331
333
332
334 def _on_key_down(self, event, skip=True):
333 def _on_key_down(self, event, skip=True):
335 """ Key press callback used for correcting behavior for
334 """ Key press callback used for correcting behavior for
336 console-like interfaces: the cursor is constraint to be after
335 console-like interfaces: the cursor is constraint to be after
337 the last prompt.
336 the last prompt.
338
337
339 Return True if event as been catched.
338 Return True if event as been catched.
340 """
339 """
341 catched = True
340 catched = True
342 # Intercept some specific keys.
341 # Intercept some specific keys.
343 if event.KeyCode == ord('L') and event.ControlDown() :
342 if event.KeyCode == ord('L') and event.ControlDown() :
344 self.scroll_to_bottom()
343 self.scroll_to_bottom()
345 elif event.KeyCode == ord('K') and event.ControlDown() :
344 elif event.KeyCode == ord('K') and event.ControlDown() :
346 self.replace_current_edit_buffer('')
345 self.input_buffer = ''
347 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
346 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
348 self.ScrollPages(-1)
347 self.ScrollPages(-1)
349 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
348 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
350 self.ScrollPages(1)
349 self.ScrollPages(1)
351 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
350 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
352 self.ScrollLines(-1)
351 self.ScrollLines(-1)
353 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
352 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
354 self.ScrollLines(1)
353 self.ScrollLines(1)
355 else:
354 else:
356 catched = False
355 catched = False
357
356
358 if self.AutoCompActive():
357 if self.AutoCompActive():
359 event.Skip()
358 event.Skip()
360 else:
359 else:
361 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
360 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
362 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
361 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
363 catched = True
362 catched = True
364 self.CallTipCancel()
363 self.CallTipCancel()
365 self.write('\n')
364 self.write('\n')
366 # Under windows scintilla seems to be doing funny stuff to the
365 # Under windows scintilla seems to be doing funny stuff to the
367 # line returns here, but get_current_edit_buffer filters this
366 # line returns here, but the getter for input_buffer filters
368 # out.
367 # this out.
369 if sys.platform == 'win32':
368 if sys.platform == 'win32':
370 self.replace_current_edit_buffer(
369 self.input_buffer = self.input_buffer
371 self.get_current_edit_buffer())
372 self._on_enter()
370 self._on_enter()
373
371
374 elif event.KeyCode == wx.WXK_HOME:
372 elif event.KeyCode == wx.WXK_HOME:
375 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
373 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
376 self.GotoPos(self.current_prompt_pos)
374 self.GotoPos(self.current_prompt_pos)
377 catched = True
375 catched = True
378
376
379 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
377 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
380 # FIXME: This behavior is not ideal: if the selection
378 # FIXME: This behavior is not ideal: if the selection
381 # is already started, it will jump.
379 # is already started, it will jump.
382 self.SetSelectionStart(self.current_prompt_pos)
380 self.SetSelectionStart(self.current_prompt_pos)
383 self.SetSelectionEnd(self.GetCurrentPos())
381 self.SetSelectionEnd(self.GetCurrentPos())
384 catched = True
382 catched = True
385
383
386 elif event.KeyCode == wx.WXK_UP:
384 elif event.KeyCode == wx.WXK_UP:
387 if self.GetCurrentLine() > self.current_prompt_line:
385 if self.GetCurrentLine() > self.current_prompt_line:
388 if self.GetCurrentLine() == self.current_prompt_line + 1 \
386 if self.GetCurrentLine() == self.current_prompt_line + 1 \
389 and self.GetColumn(self.GetCurrentPos()) < \
387 and self.GetColumn(self.GetCurrentPos()) < \
390 self.GetColumn(self.current_prompt_pos):
388 self.GetColumn(self.current_prompt_pos):
391 self.GotoPos(self.current_prompt_pos)
389 self.GotoPos(self.current_prompt_pos)
392 else:
390 else:
393 event.Skip()
391 event.Skip()
394 catched = True
392 catched = True
395
393
396 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
394 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
397 if self.GetCurrentPos() > self.current_prompt_pos:
395 if self.GetCurrentPos() > self.current_prompt_pos:
398 event.Skip()
396 event.Skip()
399 catched = True
397 catched = True
400
398
401 if skip and not catched:
399 if skip and not catched:
402 # Put the cursor back in the edit region
400 # Put the cursor back in the edit region
403 if self.GetCurrentPos() < self.current_prompt_pos:
401 if self.GetCurrentPos() < self.current_prompt_pos:
404 self.GotoPos(self.current_prompt_pos)
402 self.GotoPos(self.current_prompt_pos)
405 else:
403 else:
406 event.Skip()
404 event.Skip()
407
405
408 return catched
406 return catched
409
407
410
408
411 def _on_key_up(self, event, skip=True):
409 def _on_key_up(self, event, skip=True):
412 """ If cursor is outside the editing region, put it back.
410 """ If cursor is outside the editing region, put it back.
413 """
411 """
414 event.Skip()
412 event.Skip()
415 if self.GetCurrentPos() < self.current_prompt_pos:
413 if self.GetCurrentPos() < self.current_prompt_pos:
416 self.GotoPos(self.current_prompt_pos)
414 self.GotoPos(self.current_prompt_pos)
417
415
418
416
419
417
420 if __name__ == '__main__':
418 if __name__ == '__main__':
421 # Some simple code to test the console widget.
419 # Some simple code to test the console widget.
422 class MainWindow(wx.Frame):
420 class MainWindow(wx.Frame):
423 def __init__(self, parent, id, title):
421 def __init__(self, parent, id, title):
424 wx.Frame.__init__(self, parent, id, title, size=(300,250))
422 wx.Frame.__init__(self, parent, id, title, size=(300,250))
425 self._sizer = wx.BoxSizer(wx.VERTICAL)
423 self._sizer = wx.BoxSizer(wx.VERTICAL)
426 self.console_widget = ConsoleWidget(self)
424 self.console_widget = ConsoleWidget(self)
427 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
425 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
428 self.SetSizer(self._sizer)
426 self.SetSizer(self._sizer)
429 self.SetAutoLayout(1)
427 self.SetAutoLayout(1)
430 self.Show(True)
428 self.Show(True)
431
429
432 app = wx.PySimpleApp()
430 app = wx.PySimpleApp()
433 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
431 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
434 w.SetSize((780, 460))
432 w.SetSize((780, 460))
435 w.Show()
433 w.Show()
436
434
437 app.MainLoop()
435 app.MainLoop()
438
436
439
437
@@ -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 = False
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.get_current_edit_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,463 +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 #_COMMAND_BG = '#FAFAF1' # Nice green
46 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
46 _RUNNING_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 _RUNNING_BUFFER_MARKER = 31
50 _ERROR_MARKER = 30
50 _ERROR_MARKER = 30
51
51
52 prompt_in1 = \
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'
53 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
54
54
55 prompt_out = \
55 prompt_out = \
56 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
56 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
57
57
58 #-------------------------------------------------------------------------------
58 #-------------------------------------------------------------------------------
59 # Classes to implement the Wx frontend
59 # Classes to implement the Wx frontend
60 #-------------------------------------------------------------------------------
60 #-------------------------------------------------------------------------------
61 class WxController(PrefilterFrontEnd, ConsoleWidget):
61 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 """Classes to provide a Wx frontend to the
62 """Classes to provide a Wx frontend to the
63 IPython.kernel.core.interpreter.
63 IPython.kernel.core.interpreter.
64
64
65 This class inherits from ConsoleWidget, that provides a console-like
65 This class inherits from ConsoleWidget, that provides a console-like
66 widget to provide a text-rendering widget suitable for a terminal.
66 widget to provide a text-rendering widget suitable for a terminal.
67 """
67 """
68
68
69 output_prompt_template = string.Template(prompt_out)
69 output_prompt_template = string.Template(prompt_out)
70
70
71 input_prompt_template = string.Template(prompt_in1)
71 input_prompt_template = string.Template(prompt_in1)
72
72
73 # Print debug info on what is happening to the console.
73 # Print debug info on what is happening to the console.
74 debug = True
74 debug = True
75
75
76 # The title of the terminal, as captured through the ANSI escape
76 # The title of the terminal, as captured through the ANSI escape
77 # sequences.
77 # sequences.
78 def _set_title(self, title):
78 def _set_title(self, title):
79 return self.Parent.SetTitle(title)
79 return self.Parent.SetTitle(title)
80
80
81 def _get_title(self):
81 def _get_title(self):
82 return self.Parent.GetTitle()
82 return self.Parent.GetTitle()
83
83
84 title = property(_get_title, _set_title)
84 title = property(_get_title, _set_title)
85
85
86
87 # The buffer being edited.
88 # We are duplicating the defination here because of multiple
89 # inheritence
90 def _set_input_buffer(self, string):
91 return ConsoleWidget._set_input_buffer(self, string)
92
93 def _get_input_buffer(self):
94 """ Returns the text in current edit buffer.
95 """
96 return ConsoleWidget._get_input_buffer(self)
97
98 input_buffer = property(_get_input_buffer, _set_input_buffer)
99
100
86 #--------------------------------------------------------------------------
101 #--------------------------------------------------------------------------
87 # Private Attributes
102 # Private Attributes
88 #--------------------------------------------------------------------------
103 #--------------------------------------------------------------------------
89
104
90 # A flag governing the behavior of the input. Can be:
105 # A flag governing the behavior of the input. Can be:
91 #
106 #
92 # 'readline' for readline-like behavior with a prompt
107 # 'readline' for readline-like behavior with a prompt
93 # and an edit buffer.
108 # and an edit buffer.
94 # 'subprocess' for sending the raw input directly to a
109 # 'subprocess' for sending the raw input directly to a
95 # subprocess.
110 # subprocess.
96 # 'buffering' for buffering of the input, that will be used
111 # 'buffering' for buffering of the input, that will be used
97 # when the input state switches back to another state.
112 # when the input state switches back to another state.
98 _input_state = 'readline'
113 _input_state = 'readline'
99
114
100 # Attribute to store reference to the pipes of a subprocess, if we
115 # Attribute to store reference to the pipes of a subprocess, if we
101 # are running any.
116 # are running any.
102 _running_process = False
117 _running_process = False
103
118
104 # A queue for writing fast streams to the screen without flooding the
119 # A queue for writing fast streams to the screen without flooding the
105 # event loop
120 # event loop
106 _out_buffer = []
121 _out_buffer = []
107
122
108 # A lock to lock the _out_buffer to make sure we don't empty it
123 # A lock to lock the _out_buffer to make sure we don't empty it
109 # while it is being swapped
124 # while it is being swapped
110 _out_buffer_lock = Lock()
125 _out_buffer_lock = Lock()
111
126
112 #--------------------------------------------------------------------------
127 #--------------------------------------------------------------------------
113 # Public API
128 # Public API
114 #--------------------------------------------------------------------------
129 #--------------------------------------------------------------------------
115
130
116 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
131 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
117 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
132 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
118 *args, **kwds):
133 *args, **kwds):
119 """ Create Shell instance.
134 """ Create Shell instance.
120 """
135 """
121 ConsoleWidget.__init__(self, parent, id, pos, size, style)
136 ConsoleWidget.__init__(self, parent, id, pos, size, style)
122 PrefilterFrontEnd.__init__(self)
137 PrefilterFrontEnd.__init__(self)
123
138
124 # Marker for running buffer.
139 # Marker for running buffer.
125 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
140 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
126 background=_RUNNING_BUFFER_BG)
141 background=_RUNNING_BUFFER_BG)
127 # Marker for tracebacks.
142 # Marker for tracebacks.
128 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
143 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
129 background=_ERROR_BG)
144 background=_ERROR_BG)
130
145
131 # A time for flushing the write buffer
146 # A time for flushing the write buffer
132 BUFFER_FLUSH_TIMER_ID = 100
147 BUFFER_FLUSH_TIMER_ID = 100
133 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
148 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
134 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
149 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
135
150
136
151
137 def raw_input(self, prompt):
152 def raw_input(self, prompt):
138 """ A replacement from python's raw_input.
153 """ A replacement from python's raw_input.
139 """
154 """
140 self.new_prompt(prompt)
155 self.new_prompt(prompt)
141 self.waiting = True
156 self.waiting = True
142 self.__old_on_enter = self._on_enter
157 self.__old_on_enter = self._on_enter
143 def my_on_enter():
158 def my_on_enter():
144 self.waiting = False
159 self.waiting = False
145 self._on_enter = my_on_enter
160 self._on_enter = my_on_enter
146 # XXX: Busy waiting, ugly.
161 # XXX: Busy waiting, ugly.
147 while self.waiting:
162 while self.waiting:
148 wx.Yield()
163 wx.Yield()
149 sleep(0.1)
164 sleep(0.1)
150 self._on_enter = self.__old_on_enter
165 self._on_enter = self.__old_on_enter
151 self._input_state = 'buffering'
166 self._input_state = 'buffering'
152 return self.get_current_edit_buffer().rstrip('\n')
167 return self.input_buffer.rstrip('\n')
153
168
154
169
155 def system_call(self, command_string):
170 def system_call(self, command_string):
156 self._input_state = 'subprocess'
171 self._input_state = 'subprocess'
157 self._running_process = PipedProcess(command_string,
172 self._running_process = PipedProcess(command_string,
158 out_callback=self.buffered_write,
173 out_callback=self.buffered_write,
159 end_callback = self._end_system_call)
174 end_callback = self._end_system_call)
160 self._running_process.start()
175 self._running_process.start()
161 # XXX: another one of these polling loops to have a blocking
176 # XXX: another one of these polling loops to have a blocking
162 # call
177 # call
163 wx.Yield()
178 wx.Yield()
164 while self._running_process:
179 while self._running_process:
165 wx.Yield()
180 wx.Yield()
166 sleep(0.1)
181 sleep(0.1)
167 # Be sure to flush the buffer.
182 # Be sure to flush the buffer.
168 self._buffer_flush(event=None)
183 self._buffer_flush(event=None)
169
184
170
185
171 def do_completion(self):
186 def do_completion(self):
172 """ Do code completion on current line.
187 """ Do code completion on current line.
173 """
188 """
174 if self.debug:
189 if self.debug:
175 print >>sys.__stdout__, "do_completion",
190 print >>sys.__stdout__, "do_completion",
176 line = self.get_current_edit_buffer()
191 line = self.input_buffer
177 new_line, completions = self.complete(line)
192 new_line, completions = self.complete(line)
178 if len(completions)>1:
193 if len(completions)>1:
179 self.write_completion(completions)
194 self.write_completion(completions)
180 self.replace_current_edit_buffer(new_line)
195 self.input_buffer = new_line
181 if self.debug:
196 if self.debug:
182 print >>sys.__stdout__, completions
197 print >>sys.__stdout__, completions
183
198
184
199
185 def do_calltip(self):
200 def do_calltip(self):
186 """ Analyse current and displays useful calltip for it.
201 """ Analyse current and displays useful calltip for it.
187 """
202 """
188 if self.debug:
203 if self.debug:
189 print >>sys.__stdout__, "do_calltip"
204 print >>sys.__stdout__, "do_calltip"
190 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
205 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
191 symbol = self.get_current_edit_buffer()
206 symbol = self.input_buffer
192 symbol_string = separators.split(symbol)[-1]
207 symbol_string = separators.split(symbol)[-1]
193 base_symbol_string = symbol_string.split('.')[0]
208 base_symbol_string = symbol_string.split('.')[0]
194 if base_symbol_string in self.shell.user_ns:
209 if base_symbol_string in self.shell.user_ns:
195 symbol = self.shell.user_ns[base_symbol_string]
210 symbol = self.shell.user_ns[base_symbol_string]
196 elif base_symbol_string in self.shell.user_global_ns:
211 elif base_symbol_string in self.shell.user_global_ns:
197 symbol = self.shell.user_global_ns[base_symbol_string]
212 symbol = self.shell.user_global_ns[base_symbol_string]
198 elif base_symbol_string in __builtin__.__dict__:
213 elif base_symbol_string in __builtin__.__dict__:
199 symbol = __builtin__.__dict__[base_symbol_string]
214 symbol = __builtin__.__dict__[base_symbol_string]
200 else:
215 else:
201 return False
216 return False
202 for name in symbol_string.split('.')[1:] + ['__doc__']:
217 for name in symbol_string.split('.')[1:] + ['__doc__']:
203 symbol = getattr(symbol, name)
218 symbol = getattr(symbol, name)
204 try:
219 try:
205 self.AutoCompCancel()
220 self.AutoCompCancel()
206 wx.Yield()
221 wx.Yield()
207 self.CallTipShow(self.GetCurrentPos(), symbol)
222 self.CallTipShow(self.GetCurrentPos(), symbol)
208 except:
223 except:
209 # The retrieve symbol couldn't be converted to a string
224 # The retrieve symbol couldn't be converted to a string
210 pass
225 pass
211
226
212
227
213 def _popup_completion(self, create=False):
228 def _popup_completion(self, create=False):
214 """ Updates the popup completion menu if it exists. If create is
229 """ Updates the popup completion menu if it exists. If create is
215 true, open the menu.
230 true, open the menu.
216 """
231 """
217 if self.debug:
232 if self.debug:
218 print >>sys.__stdout__, "_popup_completion",
233 print >>sys.__stdout__, "_popup_completion",
219 line = self.get_current_edit_buffer()
234 line = self.input_buffer
220 if (self.AutoCompActive() and not line[-1] == '.') \
235 if (self.AutoCompActive() and not line[-1] == '.') \
221 or create==True:
236 or create==True:
222 suggestion, completions = self.complete(line)
237 suggestion, completions = self.complete(line)
223 offset=0
238 offset=0
224 if completions:
239 if completions:
225 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
240 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
226 residual = complete_sep.split(line)[-1]
241 residual = complete_sep.split(line)[-1]
227 offset = len(residual)
242 offset = len(residual)
228 self.pop_completion(completions, offset=offset)
243 self.pop_completion(completions, offset=offset)
229 if self.debug:
244 if self.debug:
230 print >>sys.__stdout__, completions
245 print >>sys.__stdout__, completions
231
246
232
247
233 def buffered_write(self, text):
248 def buffered_write(self, text):
234 """ A write method for streams, that caches the stream in order
249 """ A write method for streams, that caches the stream in order
235 to avoid flooding the event loop.
250 to avoid flooding the event loop.
236
251
237 This can be called outside of the main loop, in separate
252 This can be called outside of the main loop, in separate
238 threads.
253 threads.
239 """
254 """
240 self._out_buffer_lock.acquire()
255 self._out_buffer_lock.acquire()
241 self._out_buffer.append(text)
256 self._out_buffer.append(text)
242 self._out_buffer_lock.release()
257 self._out_buffer_lock.release()
243 if not self._buffer_flush_timer.IsRunning():
258 if not self._buffer_flush_timer.IsRunning():
244 wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds
259 wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds
245
260
246
261
247 #--------------------------------------------------------------------------
262 #--------------------------------------------------------------------------
248 # LineFrontEnd interface
263 # LineFrontEnd interface
249 #--------------------------------------------------------------------------
264 #--------------------------------------------------------------------------
250
265
251 def execute(self, python_string, raw_string=None):
266 def execute(self, python_string, raw_string=None):
252 self._input_state = 'buffering'
267 self._input_state = 'buffering'
253 self.CallTipCancel()
268 self.CallTipCancel()
254 self._cursor = wx.BusyCursor()
269 self._cursor = wx.BusyCursor()
255 if raw_string is None:
270 if raw_string is None:
256 raw_string = python_string
271 raw_string = python_string
257 end_line = self.current_prompt_line \
272 end_line = self.current_prompt_line \
258 + max(1, len(raw_string.split('\n'))-1)
273 + max(1, len(raw_string.split('\n'))-1)
259 for i in range(self.current_prompt_line, end_line):
274 for i in range(self.current_prompt_line, end_line):
260 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
275 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
261 # Update the display:
276 # Update the display:
262 wx.Yield()
277 wx.Yield()
263 self.GotoPos(self.GetLength())
278 self.GotoPos(self.GetLength())
264 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
279 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
265
280
266
281
267 def capture_output(self):
282 def capture_output(self):
268 self.__old_raw_input = __builtin__.raw_input
283 self.__old_raw_input = __builtin__.raw_input
269 __builtin__.raw_input = self.raw_input
284 __builtin__.raw_input = self.raw_input
270 PrefilterFrontEnd.capture_output(self)
285 PrefilterFrontEnd.capture_output(self)
271
286
272
287
273 def release_output(self):
288 def release_output(self):
274 __builtin__.raw_input = self.__old_raw_input
289 __builtin__.raw_input = self.__old_raw_input
275 PrefilterFrontEnd.capture_output(self)
290 PrefilterFrontEnd.capture_output(self)
276
291
277
292
278 def after_execute(self):
293 def after_execute(self):
279 PrefilterFrontEnd.after_execute(self)
294 PrefilterFrontEnd.after_execute(self)
280 # Clear the wait cursor
295 # Clear the wait cursor
281 if hasattr(self, '_cursor'):
296 if hasattr(self, '_cursor'):
282 del self._cursor
297 del self._cursor
283
298
284
299
285 def show_traceback(self):
300 def show_traceback(self):
286 start_line = self.GetCurrentLine()
301 start_line = self.GetCurrentLine()
287 PrefilterFrontEnd.show_traceback(self)
302 PrefilterFrontEnd.show_traceback(self)
288 wx.Yield()
303 wx.Yield()
289 for i in range(start_line, self.GetCurrentLine()):
304 for i in range(start_line, self.GetCurrentLine()):
290 self.MarkerAdd(i, _ERROR_MARKER)
305 self.MarkerAdd(i, _ERROR_MARKER)
291
306
292
307
293 def add_to_edit_buffer(self, string):
294 """ Add the given string to the current edit buffer.
295 """
296 # XXX: I should check the input_state here.
297 self.write(string)
298
299
300 #--------------------------------------------------------------------------
308 #--------------------------------------------------------------------------
301 # ConsoleWidget interface
309 # ConsoleWidget interface
302 #--------------------------------------------------------------------------
310 #--------------------------------------------------------------------------
303
311
304 def new_prompt(self, prompt):
312 def new_prompt(self, prompt):
305 """ Display a new prompt, and start a new input buffer.
313 """ Display a new prompt, and start a new input buffer.
306 """
314 """
307 self._input_state = 'readline'
315 self._input_state = 'readline'
308 ConsoleWidget.new_prompt(self, prompt)
316 ConsoleWidget.new_prompt(self, prompt)
309
317
310
318
311 def write(self, *args, **kwargs):
319 def write(self, *args, **kwargs):
312 # Avoid multiple inheritence, be explicit about which
320 # Avoid multiple inheritence, be explicit about which
313 # parent method class gets called
321 # parent method class gets called
314 ConsoleWidget.write(self, *args, **kwargs)
322 ConsoleWidget.write(self, *args, **kwargs)
315
323
316
324
317 def get_current_edit_buffer(self):
318 # Avoid multiple inheritence, be explicit about which
319 # parent method class gets called
320 return ConsoleWidget.get_current_edit_buffer(self)
321
322
323 def _on_key_down(self, event, skip=True):
325 def _on_key_down(self, event, skip=True):
324 """ Capture the character events, let the parent
326 """ Capture the character events, let the parent
325 widget handle them, and put our logic afterward.
327 widget handle them, and put our logic afterward.
326 """
328 """
327 # FIXME: This method needs to be broken down in smaller ones.
329 # FIXME: This method needs to be broken down in smaller ones.
328 current_line_number = self.GetCurrentLine()
330 current_line_number = self.GetCurrentLine()
329 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
331 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
330 # Capture Control-C
332 # Capture Control-C
331 if self._input_state == 'subprocess':
333 if self._input_state == 'subprocess':
332 if self.debug:
334 if self.debug:
333 print >>sys.__stderr__, 'Killing running process'
335 print >>sys.__stderr__, 'Killing running process'
334 self._running_process.process.kill()
336 self._running_process.process.kill()
335 elif self._input_state == 'buffering':
337 elif self._input_state == 'buffering':
336 if self.debug:
338 if self.debug:
337 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
339 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
338 raise KeyboardInterrupt
340 raise KeyboardInterrupt
339 # XXX: We need to make really sure we
341 # XXX: We need to make really sure we
340 # get back to a prompt.
342 # get back to a prompt.
341 elif self._input_state == 'subprocess' and (
343 elif self._input_state == 'subprocess' and (
342 ( event.KeyCode<256 and
344 ( event.KeyCode<256 and
343 not event.ControlDown() )
345 not event.ControlDown() )
344 or
346 or
345 ( event.KeyCode in (ord('d'), ord('D')) and
347 ( event.KeyCode in (ord('d'), ord('D')) and
346 event.ControlDown())):
348 event.ControlDown())):
347 # We are running a process, we redirect keys.
349 # We are running a process, we redirect keys.
348 ConsoleWidget._on_key_down(self, event, skip=skip)
350 ConsoleWidget._on_key_down(self, event, skip=skip)
349 char = chr(event.KeyCode)
351 char = chr(event.KeyCode)
350 # Deal with some inconsistency in wx keycodes:
352 # Deal with some inconsistency in wx keycodes:
351 if char == '\r':
353 if char == '\r':
352 char = '\n'
354 char = '\n'
353 elif not event.ShiftDown():
355 elif not event.ShiftDown():
354 char = char.lower()
356 char = char.lower()
355 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
357 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
356 char = '\04'
358 char = '\04'
357 self._running_process.process.stdin.write(char)
359 self._running_process.process.stdin.write(char)
358 self._running_process.process.stdin.flush()
360 self._running_process.process.stdin.flush()
359 elif event.KeyCode in (ord('('), 57):
361 elif event.KeyCode in (ord('('), 57):
360 # Calltips
362 # Calltips
361 event.Skip()
363 event.Skip()
362 self.do_calltip()
364 self.do_calltip()
363 elif self.AutoCompActive():
365 elif self.AutoCompActive():
364 event.Skip()
366 event.Skip()
365 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
367 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
366 wx.CallAfter(self._popup_completion, create=True)
368 wx.CallAfter(self._popup_completion, create=True)
367 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
369 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
368 wx.WXK_RIGHT):
370 wx.WXK_RIGHT):
369 wx.CallAfter(self._popup_completion)
371 wx.CallAfter(self._popup_completion)
370 else:
372 else:
371 # Up history
373 # Up history
372 if event.KeyCode == wx.WXK_UP and (
374 if event.KeyCode == wx.WXK_UP and (
373 ( current_line_number == self.current_prompt_line and
375 ( current_line_number == self.current_prompt_line and
374 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
376 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
375 or event.ControlDown() ):
377 or event.ControlDown() ):
376 new_buffer = self.get_history_previous(
378 new_buffer = self.get_history_previous(
377 self.get_current_edit_buffer())
379 self.input_buffer)
378 if new_buffer is not None:
380 if new_buffer is not None:
379 self.replace_current_edit_buffer(new_buffer)
381 self.input_buffer = new_buffer
380 if self.GetCurrentLine() > self.current_prompt_line:
382 if self.GetCurrentLine() > self.current_prompt_line:
381 # Go to first line, for seemless history up.
383 # Go to first line, for seemless history up.
382 self.GotoPos(self.current_prompt_pos)
384 self.GotoPos(self.current_prompt_pos)
383 # Down history
385 # Down history
384 elif event.KeyCode == wx.WXK_DOWN and (
386 elif event.KeyCode == wx.WXK_DOWN and (
385 ( current_line_number == self.LineCount -1 and
387 ( current_line_number == self.LineCount -1 and
386 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
388 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
387 or event.ControlDown() ):
389 or event.ControlDown() ):
388 new_buffer = self.get_history_next()
390 new_buffer = self.get_history_next()
389 if new_buffer is not None:
391 if new_buffer is not None:
390 self.replace_current_edit_buffer(new_buffer)
392 self.input_buffer = new_buffer
391 # Tab-completion
393 # Tab-completion
392 elif event.KeyCode == ord('\t'):
394 elif event.KeyCode == ord('\t'):
393 last_line = self.get_current_edit_buffer().split('\n')[-1]
395 last_line = self.input_buffer.split('\n')[-1]
394 if not re.match(r'^\s*$', last_line):
396 if not re.match(r'^\s*$', last_line):
395 self.do_completion()
397 self.do_completion()
396 else:
398 else:
397 event.Skip()
399 event.Skip()
398 else:
400 else:
399 ConsoleWidget._on_key_down(self, event, skip=skip)
401 ConsoleWidget._on_key_down(self, event, skip=skip)
400
402
401
403
402 def _on_key_up(self, event, skip=True):
404 def _on_key_up(self, event, skip=True):
403 """ Called when any key is released.
405 """ Called when any key is released.
404 """
406 """
405 if event.KeyCode in (59, ord('.')):
407 if event.KeyCode in (59, ord('.')):
406 # Intercepting '.'
408 # Intercepting '.'
407 event.Skip()
409 event.Skip()
408 self._popup_completion(create=True)
410 self._popup_completion(create=True)
409 else:
411 else:
410 ConsoleWidget._on_key_up(self, event, skip=skip)
412 ConsoleWidget._on_key_up(self, event, skip=skip)
411
413
412
414
413 def _on_enter(self):
415 def _on_enter(self):
414 """ Called on return key down, in readline input_state.
416 """ Called on return key down, in readline input_state.
415 """
417 """
416 if self.debug:
418 if self.debug:
417 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
419 print >>sys.__stdout__, repr(self.input_buffer)
418 PrefilterFrontEnd._on_enter(self)
420 PrefilterFrontEnd._on_enter(self)
419
421
420
422
421 #--------------------------------------------------------------------------
423 #--------------------------------------------------------------------------
422 # Private API
424 # Private API
423 #--------------------------------------------------------------------------
425 #--------------------------------------------------------------------------
424
426
425 def _end_system_call(self):
427 def _end_system_call(self):
426 """ Called at the end of a system call.
428 """ Called at the end of a system call.
427 """
429 """
428 self._input_state = 'buffering'
430 self._input_state = 'buffering'
429 self._running_process = False
431 self._running_process = False
430
432
431
433
432 def _buffer_flush(self, event):
434 def _buffer_flush(self, event):
433 """ Called by the timer to flush the write buffer.
435 """ Called by the timer to flush the write buffer.
434
436
435 This is always called in the mainloop, by the wx timer.
437 This is always called in the mainloop, by the wx timer.
436 """
438 """
437 self._out_buffer_lock.acquire()
439 self._out_buffer_lock.acquire()
438 _out_buffer = self._out_buffer
440 _out_buffer = self._out_buffer
439 self._out_buffer = []
441 self._out_buffer = []
440 self._out_buffer_lock.release()
442 self._out_buffer_lock.release()
441 self.write(''.join(_out_buffer), refresh=False)
443 self.write(''.join(_out_buffer), refresh=False)
442 self._buffer_flush_timer.Stop()
444 self._buffer_flush_timer.Stop()
443
445
444
446
445 if __name__ == '__main__':
447 if __name__ == '__main__':
446 class MainWindow(wx.Frame):
448 class MainWindow(wx.Frame):
447 def __init__(self, parent, id, title):
449 def __init__(self, parent, id, title):
448 wx.Frame.__init__(self, parent, id, title, size=(300,250))
450 wx.Frame.__init__(self, parent, id, title, size=(300,250))
449 self._sizer = wx.BoxSizer(wx.VERTICAL)
451 self._sizer = wx.BoxSizer(wx.VERTICAL)
450 self.shell = WxController(self)
452 self.shell = WxController(self)
451 self._sizer.Add(self.shell, 1, wx.EXPAND)
453 self._sizer.Add(self.shell, 1, wx.EXPAND)
452 self.SetSizer(self._sizer)
454 self.SetSizer(self._sizer)
453 self.SetAutoLayout(1)
455 self.SetAutoLayout(1)
454 self.Show(True)
456 self.Show(True)
455
457
456 app = wx.PySimpleApp()
458 app = wx.PySimpleApp()
457 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
459 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
458 frame.shell.SetFocus()
460 frame.shell.SetFocus()
459 frame.SetSize((680, 460))
461 frame.SetSize((680, 460))
460 self = frame.shell
462 self = frame.shell
461
463
462 app.MainLoop()
464 app.MainLoop()
463
465
General Comments 0
You need to be logged in to leave comments. Login now