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