##// END OF EJS Templates
Merging revision 1176 from lp:ipython.
Brian Granger -
r2123:822f65e6 merge
parent child Browse files
Show More
@@ -1,625 +1,624 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A Wx widget to act as a console and input commands.
3 A Wx widget to act as a console and input commands.
4
4
5 This widget deals with prompts and provides an edit buffer
5 This widget deals with prompts and provides an edit buffer
6 restricted to after the last prompt.
6 restricted to after the last prompt.
7 """
7 """
8
8
9 __docformat__ = "restructuredtext en"
9 __docformat__ = "restructuredtext en"
10
10
11 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
12 # Copyright (C) 2008 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is
14 # Distributed under the terms of the BSD License. The full license is
15 # in the file COPYING, distributed as part of this software.
15 # in the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 import wx
22 import wx
23 import wx.stc as stc
23 import wx.stc as stc
24
24
25 from wx.py import editwindow
25 from wx.py import editwindow
26 import time
26 import time
27 import sys
27 import sys
28 import string
28 import string
29
29
30 LINESEP = '\n'
30 LINESEP = '\n'
31 if sys.platform == 'win32':
31 if sys.platform == 'win32':
32 LINESEP = '\n\r'
32 LINESEP = '\n\r'
33
33
34 import re
34 import re
35
35
36 # FIXME: Need to provide an API for non user-generated display on the
36 # FIXME: Need to provide an API for non user-generated display on the
37 # screen: this should not be editable by the user.
37 # screen: this should not be editable by the user.
38 #-------------------------------------------------------------------------------
38 #-------------------------------------------------------------------------------
39 # Constants
39 # Constants
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41 _COMPLETE_BUFFER_MARKER = 31
41 _COMPLETE_BUFFER_MARKER = 31
42 _ERROR_MARKER = 30
42 _ERROR_MARKER = 30
43 _INPUT_MARKER = 29
43 _INPUT_MARKER = 29
44
44
45 _DEFAULT_SIZE = 10
45 _DEFAULT_SIZE = 10
46 if sys.platform == 'darwin':
46 if sys.platform == 'darwin':
47 _DEFAULT_SIZE = 12
47 _DEFAULT_SIZE = 12
48
48
49 _DEFAULT_STYLE = {
49 _DEFAULT_STYLE = {
50 #background definition
50 #background definition
51 'default' : 'size:%d' % _DEFAULT_SIZE,
51 'default' : 'size:%d' % _DEFAULT_SIZE,
52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
54
54
55 # Edge column: a number of None
55 # Edge column: a number of None
56 'edge_column' : -1,
56 'edge_column' : -1,
57
57
58 # properties for the various Python lexer styles
58 # properties for the various Python lexer styles
59 'comment' : 'fore:#007F00',
59 'comment' : 'fore:#007F00',
60 'number' : 'fore:#007F7F',
60 'number' : 'fore:#007F7F',
61 'string' : 'fore:#7F007F,italic',
61 'string' : 'fore:#7F007F,italic',
62 'char' : 'fore:#7F007F,italic',
62 'char' : 'fore:#7F007F,italic',
63 'keyword' : 'fore:#00007F,bold',
63 'keyword' : 'fore:#00007F,bold',
64 'triple' : 'fore:#7F0000',
64 'triple' : 'fore:#7F0000',
65 'tripledouble' : 'fore:#7F0000',
65 'tripledouble' : 'fore:#7F0000',
66 'class' : 'fore:#0000FF,bold,underline',
66 'class' : 'fore:#0000FF,bold,underline',
67 'def' : 'fore:#007F7F,bold',
67 'def' : 'fore:#007F7F,bold',
68 'operator' : 'bold',
68 'operator' : 'bold',
69
69
70 # Default colors
70 # Default colors
71 'trace' : '#FAFAF1', # Nice green
71 'trace' : '#FAFAF1', # Nice green
72 'stdout' : '#FDFFD3', # Nice yellow
72 'stdout' : '#FDFFD3', # Nice yellow
73 'stderr' : '#FFF1F1', # Nice red
73 'stderr' : '#FFF1F1', # Nice red
74
74
75 # Default scintilla settings
75 # Default scintilla settings
76 'antialiasing' : True,
76 'antialiasing' : True,
77 'carret_color' : 'BLACK',
77 'carret_color' : 'BLACK',
78 'background_color' :'WHITE',
78 'background_color' :'WHITE',
79
79
80 #prompt definition
80 #prompt definition
81 'prompt_in1' : \
81 'prompt_in1' : \
82 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02',
82 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02',
83
83
84 'prompt_out': \
84 'prompt_out': \
85 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02',
85 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02',
86 }
86 }
87
87
88 # new style numbers
88 # new style numbers
89 _STDOUT_STYLE = 15
89 _STDOUT_STYLE = 15
90 _STDERR_STYLE = 16
90 _STDERR_STYLE = 16
91 _TRACE_STYLE = 17
91 _TRACE_STYLE = 17
92
92
93
93
94 # system colors
94 # system colors
95 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
95 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
96
96
97 # Translation table from ANSI escape sequences to color.
97 # Translation table from ANSI escape sequences to color.
98 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
98 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
99 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
99 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
100 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
100 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
101 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
101 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
102 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
102 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
103 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
103 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
104 '1;34': [12, 'LIGHT BLUE'], '1;35':
104 '1;34': [12, 'LIGHT BLUE'], '1;35':
105 [13, 'MEDIUM VIOLET RED'],
105 [13, 'MEDIUM VIOLET RED'],
106 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
106 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
107
107
108 # XXX: Maybe one day we should factor this code with coloransi. Right now
108 # XXX: Maybe one day we should factor this code with coloransi. Right now
109 # coloransi is hard to reuse and makes our code more complex.
109 # coloransi is hard to reuse and makes our code more complex.
110
110
111 #we define platform specific fonts
111 #we define platform specific fonts
112 if wx.Platform == '__WXMSW__':
112 if wx.Platform == '__WXMSW__':
113 FACES = { 'times': 'Times New Roman',
113 FACES = { 'times': 'Times New Roman',
114 'mono' : 'Courier New',
114 'mono' : 'Courier New',
115 'helv' : 'Arial',
115 'helv' : 'Arial',
116 'other': 'Comic Sans MS',
116 'other': 'Comic Sans MS',
117 'size' : 10,
117 'size' : 10,
118 'size2': 8,
118 'size2': 8,
119 }
119 }
120 elif wx.Platform == '__WXMAC__':
120 elif wx.Platform == '__WXMAC__':
121 FACES = { 'times': 'Times New Roman',
121 FACES = { 'times': 'Times New Roman',
122 'mono' : 'Monaco',
122 'mono' : 'Monaco',
123 'helv' : 'Arial',
123 'helv' : 'Arial',
124 'other': 'Comic Sans MS',
124 'other': 'Comic Sans MS',
125 'size' : 10,
125 'size' : 10,
126 'size2': 8,
126 'size2': 8,
127 }
127 }
128 else:
128 else:
129 FACES = { 'times': 'Times',
129 FACES = { 'times': 'Times',
130 'mono' : 'Courier',
130 'mono' : 'Courier',
131 'helv' : 'Helvetica',
131 'helv' : 'Helvetica',
132 'other': 'new century schoolbook',
132 'other': 'new century schoolbook',
133 'size' : 10,
133 'size' : 10,
134 'size2': 8,
134 'size2': 8,
135 }
135 }
136
136
137
137
138 #-------------------------------------------------------------------------------
138 #-------------------------------------------------------------------------------
139 # The console widget class
139 # The console widget class
140 #-------------------------------------------------------------------------------
140 #-------------------------------------------------------------------------------
141 class ConsoleWidget(editwindow.EditWindow):
141 class ConsoleWidget(editwindow.EditWindow):
142 """ Specialized styled text control view for console-like workflow.
142 """ Specialized styled text control view for console-like workflow.
143
143
144 This widget is mainly interested in dealing with the prompt and
144 This widget is mainly interested in dealing with the prompt and
145 keeping the cursor inside the editing line.
145 keeping the cursor inside the editing line.
146 """
146 """
147
147
148 # This is where the title captured from the ANSI escape sequences are
148 # This is where the title captured from the ANSI escape sequences are
149 # stored.
149 # stored.
150 title = 'Console'
150 title = 'Console'
151
151
152 # Last prompt printed
152 # Last prompt printed
153 last_prompt = ''
153 last_prompt = ''
154
154
155 # The buffer being edited.
155 # The buffer being edited.
156 def _set_input_buffer(self, string):
156 def _set_input_buffer(self, string):
157 self.SetSelection(self.current_prompt_pos, self.GetLength())
157 self.SetSelection(self.current_prompt_pos, self.GetLength())
158 self.ReplaceSelection(string)
158 self.ReplaceSelection(string)
159 self.GotoPos(self.GetLength())
159 self.GotoPos(self.GetLength())
160
160
161 def _get_input_buffer(self):
161 def _get_input_buffer(self):
162 """ Returns the text in current edit buffer.
162 """ Returns the text in current edit buffer.
163 """
163 """
164 input_buffer = self.GetTextRange(self.current_prompt_pos,
164 input_buffer = self.GetTextRange(self.current_prompt_pos,
165 self.GetLength())
165 self.GetLength())
166 input_buffer = input_buffer.replace(LINESEP, '\n')
166 input_buffer = input_buffer.replace(LINESEP, '\n')
167 return input_buffer
167 return input_buffer
168
168
169 input_buffer = property(_get_input_buffer, _set_input_buffer)
169 input_buffer = property(_get_input_buffer, _set_input_buffer)
170
170
171 style = _DEFAULT_STYLE.copy()
171 style = _DEFAULT_STYLE.copy()
172
172
173 # Translation table from ANSI escape sequences to color. Override
173 # Translation table from ANSI escape sequences to color. Override
174 # this to specify your colors.
174 # this to specify your colors.
175 ANSI_STYLES = ANSI_STYLES.copy()
175 ANSI_STYLES = ANSI_STYLES.copy()
176
176
177 # Font faces
177 # Font faces
178 faces = FACES.copy()
178 faces = FACES.copy()
179
179
180 # Store the last time a refresh was done
180 # Store the last time a refresh was done
181 _last_refresh_time = 0
181 _last_refresh_time = 0
182
182
183 #--------------------------------------------------------------------------
183 #--------------------------------------------------------------------------
184 # Public API
184 # Public API
185 #--------------------------------------------------------------------------
185 #--------------------------------------------------------------------------
186
186
187 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
187 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
188 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
188 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
189 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
189 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
190 self.configure_scintilla()
190 self.configure_scintilla()
191 # Track if 'enter' key as ever been processed
191 # Track if 'enter' key as ever been processed
192 # This variable will only be reallowed until key goes up
192 # This variable will only be reallowed until key goes up
193 self.enter_catched = False
193 self.enter_catched = False
194 self.current_prompt_pos = 0
194 self.current_prompt_pos = 0
195
195
196 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
196 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
197 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
197 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
198
198
199
199
200 def write(self, text, refresh=True):
200 def write(self, text, refresh=True):
201 """ Write given text to buffer, while translating the ansi escape
201 """ Write given text to buffer, while translating the ansi escape
202 sequences.
202 sequences.
203 """
203 """
204 # XXX: do not put print statements to sys.stdout/sys.stderr in
204 # XXX: do not put print statements to sys.stdout/sys.stderr in
205 # this method, the print statements will call this method, as
205 # this method, the print statements will call this method, as
206 # you will end up with an infinit loop
206 # you will end up with an infinit loop
207 title = self.title_pat.split(text)
207 title = self.title_pat.split(text)
208 if len(title)>1:
208 if len(title)>1:
209 self.title = title[-2]
209 self.title = title[-2]
210
210
211 text = self.title_pat.sub('', text)
211 text = self.title_pat.sub('', text)
212 segments = self.color_pat.split(text)
212 segments = self.color_pat.split(text)
213 segment = segments.pop(0)
213 segment = segments.pop(0)
214 self.GotoPos(self.GetLength())
214 self.GotoPos(self.GetLength())
215 self.StartStyling(self.GetLength(), 0xFF)
215 self.StartStyling(self.GetLength(), 0xFF)
216 try:
216 try:
217 self.AppendText(segment)
217 self.AppendText(segment)
218 except UnicodeDecodeError:
218 except UnicodeDecodeError:
219 # XXX: Do I really want to skip the exception?
219 # XXX: Do I really want to skip the exception?
220 pass
220 pass
221
221
222 if segments:
222 if segments:
223 for ansi_tag, text in zip(segments[::2], segments[1::2]):
223 for ansi_tag, text in zip(segments[::2], segments[1::2]):
224 self.StartStyling(self.GetLength(), 0xFF)
224 self.StartStyling(self.GetLength(), 0xFF)
225 try:
225 try:
226 self.AppendText(text)
226 self.AppendText(text)
227 except UnicodeDecodeError:
227 except UnicodeDecodeError:
228 # XXX: Do I really want to skip the exception?
228 # XXX: Do I really want to skip the exception?
229 pass
229 pass
230
230
231 if ansi_tag not in self.ANSI_STYLES:
231 if ansi_tag not in self.ANSI_STYLES:
232 style = 0
232 style = 0
233 else:
233 else:
234 style = self.ANSI_STYLES[ansi_tag][0]
234 style = self.ANSI_STYLES[ansi_tag][0]
235
235
236 self.SetStyling(len(text), style)
236 self.SetStyling(len(text), style)
237
237
238 self.GotoPos(self.GetLength())
238 self.GotoPos(self.GetLength())
239 if refresh:
239 if refresh:
240 current_time = time.time()
240 current_time = time.time()
241 if current_time - self._last_refresh_time > 0.03:
241 if current_time - self._last_refresh_time > 0.03:
242 if sys.platform == 'win32':
242 if sys.platform == 'win32':
243 wx.SafeYield()
243 wx.SafeYield()
244 else:
244 else:
245 wx.Yield()
245 wx.Yield()
246 # self.ProcessEvent(wx.PaintEvent())
246 # self.ProcessEvent(wx.PaintEvent())
247 self._last_refresh_time = current_time
247 self._last_refresh_time = current_time
248
248
249
249
250 def new_prompt(self, prompt):
250 def new_prompt(self, prompt):
251 """ Prints a prompt at start of line, and move the start of the
251 """ Prints a prompt at start of line, and move the start of the
252 current block there.
252 current block there.
253
253
254 The prompt can be given with ascii escape sequences.
254 The prompt can be given with ascii escape sequences.
255 """
255 """
256 self.write(prompt, refresh=False)
256 self.write(prompt, refresh=False)
257 # now we update our cursor giving end of prompt
257 # now we update our cursor giving end of prompt
258 self.current_prompt_pos = self.GetLength()
258 self.current_prompt_pos = self.GetLength()
259 self.current_prompt_line = self.GetCurrentLine()
259 self.current_prompt_line = self.GetCurrentLine()
260 self.EnsureCaretVisible()
260 self.EnsureCaretVisible()
261 self.last_prompt = prompt
261 self.last_prompt = prompt
262
262
263
263
264 def continuation_prompt(self):
264 def continuation_prompt(self):
265 """ Returns the current continuation prompt.
265 """ Returns the current continuation prompt.
266 We need to implement this method here to deal with the
266 We need to implement this method here to deal with the
267 ascii escape sequences cleaning up.
267 ascii escape sequences cleaning up.
268 """
268 """
269 # ASCII-less prompt
269 # ASCII-less prompt
270 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
270 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
271 return "."*(len(ascii_less)-2) + ': '
271 return "."*(len(ascii_less)-2) + ': '
272
272
273
273
274 def scroll_to_bottom(self):
274 def scroll_to_bottom(self):
275 maxrange = self.GetScrollRange(wx.VERTICAL)
275 maxrange = self.GetScrollRange(wx.VERTICAL)
276 self.ScrollLines(maxrange)
276 self.ScrollLines(maxrange)
277
277
278
278
279 def pop_completion(self, possibilities, offset=0):
279 def pop_completion(self, possibilities, offset=0):
280 """ Pops up an autocompletion menu. Offset is the offset
280 """ Pops up an autocompletion menu. Offset is the offset
281 in characters of the position at which the menu should
281 in characters of the position at which the menu should
282 appear, relativ to the cursor.
282 appear, relativ to the cursor.
283 """
283 """
284 self.AutoCompSetIgnoreCase(False)
284 self.AutoCompSetIgnoreCase(False)
285 self.AutoCompSetAutoHide(False)
285 self.AutoCompSetAutoHide(False)
286 self.AutoCompSetMaxHeight(len(possibilities))
286 self.AutoCompSetMaxHeight(len(possibilities))
287 self.AutoCompShow(offset, " ".join(possibilities))
287 self.AutoCompShow(offset, " ".join(possibilities))
288
288
289
289
290 def get_line_width(self):
290 def get_line_width(self):
291 """ Return the width of the line in characters.
291 """ Return the width of the line in characters.
292 """
292 """
293 return self.GetSize()[0]/self.GetCharWidth()
293 return self.GetSize()[0]/self.GetCharWidth()
294
294
295
295
296 def configure_scintilla(self):
296 def configure_scintilla(self):
297 """ Set up all the styling option of the embedded scintilla
297 """ Set up all the styling option of the embedded scintilla
298 widget.
298 widget.
299 """
299 """
300 p = self.style.copy()
300 p = self.style.copy()
301
301
302 # Marker for complete buffer.
302 # Marker for complete buffer.
303 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
303 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
304 background=p['trace'])
304 background=p['trace'])
305
305
306 # Marker for current input buffer.
306 # Marker for current input buffer.
307 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
307 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
308 background=p['stdout'])
308 background=p['stdout'])
309 # Marker for tracebacks.
309 # Marker for tracebacks.
310 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
310 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
311 background=p['stderr'])
311 background=p['stderr'])
312
312
313 self.SetEOLMode(stc.STC_EOL_LF)
313 self.SetEOLMode(stc.STC_EOL_LF)
314
314
315 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
315 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
316 # the widget
316 # the widget
317 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
317 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
318 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
318 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
319 # Also allow Ctrl Shift "=" for poor non US keyboard users.
319 # Also allow Ctrl Shift "=" for poor non US keyboard users.
320 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
320 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
321 stc.STC_CMD_ZOOMIN)
321 stc.STC_CMD_ZOOMIN)
322
322
323 # Keys: we need to clear some of the keys the that don't play
323 # Keys: we need to clear some of the keys the that don't play
324 # well with a console.
324 # well with a console.
325 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
325 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
326 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
326 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
327 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
327 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
328 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
328 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
329
329
330 self.SetEOLMode(stc.STC_EOL_CRLF)
330 self.SetEOLMode(stc.STC_EOL_CRLF)
331 self.SetWrapMode(stc.STC_WRAP_CHAR)
331 self.SetWrapMode(stc.STC_WRAP_CHAR)
332 self.SetWrapMode(stc.STC_WRAP_WORD)
332 self.SetWrapMode(stc.STC_WRAP_WORD)
333 self.SetBufferedDraw(True)
333 self.SetBufferedDraw(True)
334
334
335 self.SetUseAntiAliasing(p['antialiasing'])
335 self.SetUseAntiAliasing(p['antialiasing'])
336
336
337 self.SetLayoutCache(stc.STC_CACHE_PAGE)
337 self.SetLayoutCache(stc.STC_CACHE_PAGE)
338 self.SetUndoCollection(False)
338 self.SetUndoCollection(False)
339 self.SetUseTabs(True)
339 self.SetUseTabs(True)
340 self.SetIndent(4)
340 self.SetIndent(4)
341 self.SetTabWidth(4)
341 self.SetTabWidth(4)
342
342
343 # we don't want scintilla's autocompletion to choose
343 # we don't want scintilla's autocompletion to choose
344 # automaticaly out of a single choice list, as we pop it up
344 # automaticaly out of a single choice list, as we pop it up
345 # automaticaly
345 # automaticaly
346 self.AutoCompSetChooseSingle(False)
346 self.AutoCompSetChooseSingle(False)
347 self.AutoCompSetMaxHeight(10)
347 self.AutoCompSetMaxHeight(10)
348 # XXX: this doesn't seem to have an effect.
348 # XXX: this doesn't seem to have an effect.
349 self.AutoCompSetFillUps('\n')
349 self.AutoCompSetFillUps('\n')
350
350
351 self.SetMargins(3, 3) #text is moved away from border with 3px
351 self.SetMargins(3, 3) #text is moved away from border with 3px
352 # Suppressing Scintilla margins
352 # Suppressing Scintilla margins
353 self.SetMarginWidth(0, 0)
353 self.SetMarginWidth(0, 0)
354 self.SetMarginWidth(1, 0)
354 self.SetMarginWidth(1, 0)
355 self.SetMarginWidth(2, 0)
355 self.SetMarginWidth(2, 0)
356
356
357 # Xterm escape sequences
357 # Xterm escape sequences
358 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
358 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
359 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
359 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
360
360
361 # styles
361 # styles
362
362
363 self.SetCaretForeground(p['carret_color'])
363 self.SetCaretForeground(p['carret_color'])
364
364
365 background_color = p['background_color']
365 background_color = p['background_color']
366
366
367 if 'default' in p:
367 if 'default' in p:
368 if 'back' not in p['default']:
368 if 'back' not in p['default']:
369 p['default'] += ',back:%s' % background_color
369 p['default'] += ',back:%s' % background_color
370 if 'size' not in p['default']:
370 if 'size' not in p['default']:
371 p['default'] += ',size:%s' % self.faces['size']
371 p['default'] += ',size:%s' % self.faces['size']
372 if 'face' not in p['default']:
372 if 'face' not in p['default']:
373 p['default'] += ',face:%s' % self.faces['mono']
373 p['default'] += ',face:%s' % self.faces['mono']
374
374
375 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
375 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
376 else:
376 else:
377 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
377 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
378 "fore:%s,back:%s,size:%d,face:%s"
378 "fore:%s,back:%s,size:%d,face:%s"
379 % (self.ANSI_STYLES['0;30'][1],
379 % (self.ANSI_STYLES['0;30'][1],
380 background_color,
380 background_color,
381 self.faces['size'], self.faces['mono']))
381 self.faces['size'], self.faces['mono']))
382
382
383 self.StyleClearAll()
383 self.StyleClearAll()
384
384
385 # XXX: two lines below are usefull if not using the lexer
385 # XXX: two lines below are usefull if not using the lexer
386 #for style in self.ANSI_STYLES.values():
386 #for style in self.ANSI_STYLES.values():
387 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
387 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
388
388
389 # prompt definition
389 # prompt definition
390 self.prompt_in1 = p['prompt_in1']
390 self.prompt_in1 = p['prompt_in1']
391 self.prompt_out = p['prompt_out']
391 self.prompt_out = p['prompt_out']
392
392
393 self.output_prompt_template = string.Template(self.prompt_out)
393 self.output_prompt_template = string.Template(self.prompt_out)
394 self.input_prompt_template = string.Template(self.prompt_in1)
394 self.input_prompt_template = string.Template(self.prompt_in1)
395
395
396 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
396 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
397 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
397 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
398 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
398 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
399 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
399 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
400 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
400 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
401 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
401 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
402 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
402 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
403 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
403 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
404 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
404 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
405 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
405 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
406 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
406 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
407 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
407 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
408 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
408 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
409 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
409 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
410 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
410 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
411 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
411 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
412 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
412 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
413
413
414 edge_column = p['edge_column']
414 edge_column = p['edge_column']
415 if edge_column is not None and edge_column > 0:
415 if edge_column is not None and edge_column > 0:
416 #we add a vertical line to console widget
416 #we add a vertical line to console widget
417 self.SetEdgeMode(stc.STC_EDGE_LINE)
417 self.SetEdgeMode(stc.STC_EDGE_LINE)
418 self.SetEdgeColumn(edge_column)
418 self.SetEdgeColumn(edge_column)
419
419
420
420
421 #--------------------------------------------------------------------------
421 #--------------------------------------------------------------------------
422 # EditWindow API
422 # EditWindow API
423 #--------------------------------------------------------------------------
423 #--------------------------------------------------------------------------
424
424
425 def OnUpdateUI(self, event):
425 def OnUpdateUI(self, event):
426 """ Override the OnUpdateUI of the EditWindow class, to prevent
426 """ Override the OnUpdateUI of the EditWindow class, to prevent
427 syntax highlighting both for faster redraw, and for more
427 syntax highlighting both for faster redraw, and for more
428 consistent look and feel.
428 consistent look and feel.
429 """
429 """
430
430
431
431
432 #--------------------------------------------------------------------------
432 #--------------------------------------------------------------------------
433 # Private API
433 # Private API
434 #--------------------------------------------------------------------------
434 #--------------------------------------------------------------------------
435
435
436 def _on_key_down(self, event, skip=True):
436 def _on_key_down(self, event, skip=True):
437 """ Key press callback used for correcting behavior for
437 """ Key press callback used for correcting behavior for
438 console-like interfaces: the cursor is constraint to be after
438 console-like interfaces: the cursor is constraint to be after
439 the last prompt.
439 the last prompt.
440
440
441 Return True if event as been catched.
441 Return True if event as been catched.
442 """
442 """
443 catched = True
443 catched = True
444 # XXX: Would the right way to do this be to have a
444 # XXX: Would the right way to do this be to have a
445 # dictionary at the instance level associating keys with
445 # dictionary at the instance level associating keys with
446 # callbacks? How would we deal with inheritance? And Do the
446 # callbacks? How would we deal with inheritance? And Do the
447 # different callbacks share local variables?
447 # different callbacks share local variables?
448
448
449 # Intercept some specific keys.
449 # Intercept some specific keys.
450 if event.KeyCode == ord('L') and event.ControlDown() :
450 key_code = event.GetKeyCode()
451 if key_code == ord('L') and event.ControlDown() :
451 self.scroll_to_bottom()
452 self.scroll_to_bottom()
452 elif event.KeyCode == ord('K') and event.ControlDown() :
453 elif key_code == ord('K') and event.ControlDown() :
453 self.input_buffer = ''
454 self.input_buffer = ''
454 elif event.KeyCode == ord('A') and event.ControlDown() :
455 elif key_code == ord('A') and event.ControlDown() :
455 self.GotoPos(self.GetLength())
456 self.GotoPos(self.GetLength())
456 self.SetSelectionStart(self.current_prompt_pos)
457 self.SetSelectionStart(self.current_prompt_pos)
457 self.SetSelectionEnd(self.GetCurrentPos())
458 self.SetSelectionEnd(self.GetCurrentPos())
458 catched = True
459 catched = True
459 elif event.KeyCode == ord('E') and event.ControlDown() :
460 elif key_code == ord('E') and event.ControlDown() :
460 self.GotoPos(self.GetLength())
461 self.GotoPos(self.GetLength())
461 catched = True
462 catched = True
462 elif event.KeyCode == wx.WXK_PAGEUP:
463 elif key_code == wx.WXK_PAGEUP:
463 self.ScrollPages(-1)
464 self.ScrollPages(-1)
464 elif event.KeyCode == wx.WXK_PAGEDOWN:
465 elif key_code == wx.WXK_PAGEDOWN:
465 self.ScrollPages(1)
466 self.ScrollPages(1)
466 elif event.KeyCode == wx.WXK_HOME:
467 elif key_code == wx.WXK_HOME:
467 self.GotoPos(self.GetLength())
468 self.GotoPos(self.GetLength())
468 elif event.KeyCode == wx.WXK_END:
469 elif key_code == wx.WXK_END:
469 self.GotoPos(self.GetLength())
470 self.GotoPos(self.GetLength())
470 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
471 elif key_code == wx.WXK_UP and event.ShiftDown():
471 self.ScrollLines(-1)
472 self.ScrollLines(-1)
472 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
473 elif key_code == wx.WXK_DOWN and event.ShiftDown():
473 self.ScrollLines(1)
474 self.ScrollLines(1)
474 else:
475 else:
475 catched = False
476 catched = False
476
477
477 if self.AutoCompActive():
478 if self.AutoCompActive():
478 event.Skip()
479 event.Skip()
479 else:
480 else:
480 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
481 if key_code in (13, wx.WXK_NUMPAD_ENTER):
481 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
482 # XXX: not catching modifiers, to be wx2.6-compatible
482 wx.MOD_SHIFT):
483 catched = True
483 catched = True
484 if not self.enter_catched:
484 if not self.enter_catched:
485 self.CallTipCancel()
485 self.CallTipCancel()
486 if event.Modifiers == wx.MOD_SHIFT:
486 if event.ShiftDown():
487 # Try to force execution
487 # Try to force execution
488 self.GotoPos(self.GetLength())
488 self.GotoPos(self.GetLength())
489 self.write('\n' + self.continuation_prompt(),
489 self.write('\n' + self.continuation_prompt(),
490 refresh=False)
490 refresh=False)
491 self._on_enter()
491 self._on_enter()
492 else:
492 else:
493 self._on_enter()
493 self._on_enter()
494 self.enter_catched = True
494 self.enter_catched = True
495
495
496 elif event.KeyCode == wx.WXK_HOME:
496 elif key_code == wx.WXK_HOME:
497 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
497 if not event.ShiftDown():
498 self.GotoPos(self.current_prompt_pos)
498 self.GotoPos(self.current_prompt_pos)
499 catched = True
499 catched = True
500
500 else:
501 elif event.Modifiers == wx.MOD_SHIFT:
502 # FIXME: This behavior is not ideal: if the selection
501 # FIXME: This behavior is not ideal: if the selection
503 # is already started, it will jump.
502 # is already started, it will jump.
504 self.SetSelectionStart(self.current_prompt_pos)
503 self.SetSelectionStart(self.current_prompt_pos)
505 self.SetSelectionEnd(self.GetCurrentPos())
504 self.SetSelectionEnd(self.GetCurrentPos())
506 catched = True
505 catched = True
507
506
508 elif event.KeyCode == wx.WXK_UP:
507 elif key_code == wx.WXK_UP:
509 if self.GetCurrentLine() > self.current_prompt_line:
508 if self.GetCurrentLine() > self.current_prompt_line:
510 if self.GetCurrentLine() == self.current_prompt_line + 1 \
509 if self.GetCurrentLine() == self.current_prompt_line + 1 \
511 and self.GetColumn(self.GetCurrentPos()) < \
510 and self.GetColumn(self.GetCurrentPos()) < \
512 self.GetColumn(self.current_prompt_pos):
511 self.GetColumn(self.current_prompt_pos):
513 self.GotoPos(self.current_prompt_pos)
512 self.GotoPos(self.current_prompt_pos)
514 else:
513 else:
515 event.Skip()
514 event.Skip()
516 catched = True
515 catched = True
517
516
518 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
517 elif key_code in (wx.WXK_LEFT, wx.WXK_BACK):
519 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
518 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
520 event.Skip()
519 event.Skip()
521 catched = True
520 catched = True
522
521
523 elif event.KeyCode == wx.WXK_RIGHT:
522 elif key_code == wx.WXK_RIGHT:
524 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
523 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
525 event.Skip()
524 event.Skip()
526 catched = True
525 catched = True
527
526
528
527
529 elif event.KeyCode == wx.WXK_DELETE:
528 elif key_code == wx.WXK_DELETE:
530 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
529 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
531 event.Skip()
530 event.Skip()
532 catched = True
531 catched = True
533
532
534 if skip and not catched:
533 if skip and not catched:
535 # Put the cursor back in the edit region
534 # Put the cursor back in the edit region
536 if not self._keep_cursor_in_buffer():
535 if not self._keep_cursor_in_buffer():
537 if not (self.GetCurrentPos() == self.GetLength()
536 if not (self.GetCurrentPos() == self.GetLength()
538 and event.KeyCode == wx.WXK_DELETE):
537 and key_code == wx.WXK_DELETE):
539 event.Skip()
538 event.Skip()
540 catched = True
539 catched = True
541
540
542 return catched
541 return catched
543
542
544
543
545 def _on_key_up(self, event, skip=True):
544 def _on_key_up(self, event, skip=True):
546 """ If cursor is outside the editing region, put it back.
545 """ If cursor is outside the editing region, put it back.
547 """
546 """
548 if skip:
547 if skip:
549 event.Skip()
548 event.Skip()
550 self._keep_cursor_in_buffer()
549 self._keep_cursor_in_buffer()
551
550
552
551
553 # XXX: I need to avoid the problem of having an empty glass;
552 # XXX: I need to avoid the problem of having an empty glass;
554 def _keep_cursor_in_buffer(self, pos=None):
553 def _keep_cursor_in_buffer(self, pos=None):
555 """ Checks if the cursor is where it is allowed to be. If not,
554 """ Checks if the cursor is where it is allowed to be. If not,
556 put it back.
555 put it back.
557
556
558 Returns
557 Returns
559 -------
558 -------
560 cursor_moved: Boolean
559 cursor_moved: Boolean
561 whether or not the cursor was moved by this routine.
560 whether or not the cursor was moved by this routine.
562
561
563 Notes
562 Notes
564 ------
563 ------
565 WARNING: This does proper checks only for horizontal
564 WARNING: This does proper checks only for horizontal
566 movements.
565 movements.
567 """
566 """
568 if pos is None:
567 if pos is None:
569 current_pos = self.GetCurrentPos()
568 current_pos = self.GetCurrentPos()
570 else:
569 else:
571 current_pos = pos
570 current_pos = pos
572 if current_pos < self.current_prompt_pos:
571 if current_pos < self.current_prompt_pos:
573 self.GotoPos(self.current_prompt_pos)
572 self.GotoPos(self.current_prompt_pos)
574 return True
573 return True
575 line_num = self.LineFromPosition(current_pos)
574 line_num = self.LineFromPosition(current_pos)
576 if not current_pos > self.GetLength():
575 if not current_pos > self.GetLength():
577 line_pos = self.GetColumn(current_pos)
576 line_pos = self.GetColumn(current_pos)
578 else:
577 else:
579 line_pos = self.GetColumn(self.GetLength())
578 line_pos = self.GetColumn(self.GetLength())
580 line = self.GetLine(line_num)
579 line = self.GetLine(line_num)
581 # Jump the continuation prompt
580 # Jump the continuation prompt
582 continuation_prompt = self.continuation_prompt()
581 continuation_prompt = self.continuation_prompt()
583 if ( line.startswith(continuation_prompt)
582 if ( line.startswith(continuation_prompt)
584 and line_pos < len(continuation_prompt)):
583 and line_pos < len(continuation_prompt)):
585 if line_pos < 2:
584 if line_pos < 2:
586 # We are at the beginning of the line, trying to move
585 # We are at the beginning of the line, trying to move
587 # forward: jump forward.
586 # forward: jump forward.
588 self.GotoPos(current_pos + 1 +
587 self.GotoPos(current_pos + 1 +
589 len(continuation_prompt) - line_pos)
588 len(continuation_prompt) - line_pos)
590 else:
589 else:
591 # Jump back up
590 # Jump back up
592 self.GotoPos(self.GetLineEndPosition(line_num-1))
591 self.GotoPos(self.GetLineEndPosition(line_num-1))
593 return True
592 return True
594 elif ( current_pos > self.GetLineEndPosition(line_num)
593 elif ( current_pos > self.GetLineEndPosition(line_num)
595 and not current_pos == self.GetLength()):
594 and not current_pos == self.GetLength()):
596 # Jump to next line
595 # Jump to next line
597 self.GotoPos(current_pos + 1 +
596 self.GotoPos(current_pos + 1 +
598 len(continuation_prompt))
597 len(continuation_prompt))
599 return True
598 return True
600
599
601 # We re-allow enter event processing
600 # We re-allow enter event processing
602 self.enter_catched = False
601 self.enter_catched = False
603 return False
602 return False
604
603
605
604
606 if __name__ == '__main__':
605 if __name__ == '__main__':
607 # Some simple code to test the console widget.
606 # Some simple code to test the console widget.
608 class MainWindow(wx.Frame):
607 class MainWindow(wx.Frame):
609 def __init__(self, parent, id, title):
608 def __init__(self, parent, id, title):
610 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
609 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
611 self._sizer = wx.BoxSizer(wx.VERTICAL)
610 self._sizer = wx.BoxSizer(wx.VERTICAL)
612 self.console_widget = ConsoleWidget(self)
611 self.console_widget = ConsoleWidget(self)
613 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
612 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
614 self.SetSizer(self._sizer)
613 self.SetSizer(self._sizer)
615 self.SetAutoLayout(1)
614 self.SetAutoLayout(1)
616 self.Show(True)
615 self.Show(True)
617
616
618 app = wx.PySimpleApp()
617 app = wx.PySimpleApp()
619 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
618 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
620 w.SetSize((780, 460))
619 w.SetSize((780, 460))
621 w.Show()
620 w.Show()
622
621
623 app.MainLoop()
622 app.MainLoop()
624
623
625
624
@@ -1,601 +1,602 b''
1 # encoding: utf-8 -*- test-case-name:
1 # encoding: utf-8 -*- test-case-name:
2 # FIXME: Need to add tests.
2 # FIXME: Need to add tests.
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4
4
5 """Classes to provide a Wx frontend to the
5 """Classes to provide a Wx frontend to the
6 IPython.kernel.core.interpreter.
6 IPython.kernel.core.interpreter.
7
7
8 This class inherits from ConsoleWidget, that provides a console-like
8 This class inherits from ConsoleWidget, that provides a console-like
9 widget to provide a text-rendering widget suitable for a terminal.
9 widget to provide a text-rendering widget suitable for a terminal.
10 """
10 """
11
11
12 __docformat__ = "restructuredtext en"
12 __docformat__ = "restructuredtext en"
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20
20
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24
24
25 # Major library imports
25 # Major library imports
26 import re
26 import re
27 import __builtin__
27 import __builtin__
28 import sys
28 import sys
29 from threading import Lock
29 from threading import Lock
30
30
31 import wx
31 import wx
32 from wx import stc
32 from wx import stc
33
33
34 # Ipython-specific imports.
34 # Ipython-specific imports.
35 from IPython.frontend.process import PipedProcess
35 from IPython.frontend.process import PipedProcess
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 _ERROR_MARKER, _INPUT_MARKER
37 _ERROR_MARKER, _INPUT_MARKER
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39
39
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41 # Classes to implement the Wx frontend
41 # Classes to implement the Wx frontend
42 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
43 class WxController(ConsoleWidget, PrefilterFrontEnd):
43 class WxController(ConsoleWidget, PrefilterFrontEnd):
44 """Classes to provide a Wx frontend to the
44 """Classes to provide a Wx frontend to the
45 IPython.kernel.core.interpreter.
45 IPython.kernel.core.interpreter.
46
46
47 This class inherits from ConsoleWidget, that provides a console-like
47 This class inherits from ConsoleWidget, that provides a console-like
48 widget to provide a text-rendering widget suitable for a terminal.
48 widget to provide a text-rendering widget suitable for a terminal.
49 """
49 """
50
50
51 # Print debug info on what is happening to the console.
51 # Print debug info on what is happening to the console.
52 debug = False
52 debug = False
53
53
54 # The title of the terminal, as captured through the ANSI escape
54 # The title of the terminal, as captured through the ANSI escape
55 # sequences.
55 # sequences.
56 def _set_title(self, title):
56 def _set_title(self, title):
57 return self.Parent.SetTitle(title)
57 return self.Parent.SetTitle(title)
58
58
59 def _get_title(self):
59 def _get_title(self):
60 return self.Parent.GetTitle()
60 return self.Parent.GetTitle()
61
61
62 title = property(_get_title, _set_title)
62 title = property(_get_title, _set_title)
63
63
64
64
65 # The buffer being edited.
65 # The buffer being edited.
66 # We are duplicating the definition here because of multiple
66 # We are duplicating the definition here because of multiple
67 # inheritence
67 # inheritence
68 def _set_input_buffer(self, string):
68 def _set_input_buffer(self, string):
69 ConsoleWidget._set_input_buffer(self, string)
69 ConsoleWidget._set_input_buffer(self, string)
70 self._colorize_input_buffer()
70 self._colorize_input_buffer()
71
71
72 def _get_input_buffer(self):
72 def _get_input_buffer(self):
73 """ Returns the text in current edit buffer.
73 """ Returns the text in current edit buffer.
74 """
74 """
75 return ConsoleWidget._get_input_buffer(self)
75 return ConsoleWidget._get_input_buffer(self)
76
76
77 input_buffer = property(_get_input_buffer, _set_input_buffer)
77 input_buffer = property(_get_input_buffer, _set_input_buffer)
78
78
79
79
80 #--------------------------------------------------------------------------
80 #--------------------------------------------------------------------------
81 # Private Attributes
81 # Private Attributes
82 #--------------------------------------------------------------------------
82 #--------------------------------------------------------------------------
83
83
84 # A flag governing the behavior of the input. Can be:
84 # A flag governing the behavior of the input. Can be:
85 #
85 #
86 # 'readline' for readline-like behavior with a prompt
86 # 'readline' for readline-like behavior with a prompt
87 # and an edit buffer.
87 # and an edit buffer.
88 # 'raw_input' similar to readline, but triggered by a raw-input
88 # 'raw_input' similar to readline, but triggered by a raw-input
89 # call. Can be used by subclasses to act differently.
89 # call. Can be used by subclasses to act differently.
90 # 'subprocess' for sending the raw input directly to a
90 # 'subprocess' for sending the raw input directly to a
91 # subprocess.
91 # subprocess.
92 # 'buffering' for buffering of the input, that will be used
92 # 'buffering' for buffering of the input, that will be used
93 # when the input state switches back to another state.
93 # when the input state switches back to another state.
94 _input_state = 'readline'
94 _input_state = 'readline'
95
95
96 # Attribute to store reference to the pipes of a subprocess, if we
96 # Attribute to store reference to the pipes of a subprocess, if we
97 # are running any.
97 # are running any.
98 _running_process = False
98 _running_process = False
99
99
100 # A queue for writing fast streams to the screen without flooding the
100 # A queue for writing fast streams to the screen without flooding the
101 # event loop
101 # event loop
102 _out_buffer = []
102 _out_buffer = []
103
103
104 # A lock to lock the _out_buffer to make sure we don't empty it
104 # A lock to lock the _out_buffer to make sure we don't empty it
105 # while it is being swapped
105 # while it is being swapped
106 _out_buffer_lock = Lock()
106 _out_buffer_lock = Lock()
107
107
108 # The different line markers used to higlight the prompts.
108 # The different line markers used to higlight the prompts.
109 _markers = dict()
109 _markers = dict()
110
110
111 #--------------------------------------------------------------------------
111 #--------------------------------------------------------------------------
112 # Public API
112 # Public API
113 #--------------------------------------------------------------------------
113 #--------------------------------------------------------------------------
114
114
115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
116 size=wx.DefaultSize,
116 size=wx.DefaultSize,
117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 styledef=None,
118 styledef=None,
119 *args, **kwds):
119 *args, **kwds):
120 """ Create Shell instance.
120 """ Create Shell instance.
121
121
122 Parameters
122 Parameters
123 -----------
123 -----------
124 styledef : dict, optional
124 styledef : dict, optional
125 styledef is the dictionary of options used to define the
125 styledef is the dictionary of options used to define the
126 style.
126 style.
127 """
127 """
128 if styledef is not None:
128 if styledef is not None:
129 self.style = styledef
129 self.style = styledef
130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
131 PrefilterFrontEnd.__init__(self, **kwds)
131 PrefilterFrontEnd.__init__(self, **kwds)
132
132
133 # Stick in our own raw_input:
133 # Stick in our own raw_input:
134 self.ipython0.raw_input = self.raw_input
134 self.ipython0.raw_input = self.raw_input
135
135
136 # A time for flushing the write buffer
136 # A time for flushing the write buffer
137 BUFFER_FLUSH_TIMER_ID = 100
137 BUFFER_FLUSH_TIMER_ID = 100
138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
140
140
141 if 'debug' in kwds:
141 if 'debug' in kwds:
142 self.debug = kwds['debug']
142 self.debug = kwds['debug']
143 kwds.pop('debug')
143 kwds.pop('debug')
144
144
145 # Inject self in namespace, for debug
145 # Inject self in namespace, for debug
146 if self.debug:
146 if self.debug:
147 self.shell.user_ns['self'] = self
147 self.shell.user_ns['self'] = self
148 # Inject our own raw_input in namespace
148 # Inject our own raw_input in namespace
149 self.shell.user_ns['raw_input'] = self.raw_input
149 self.shell.user_ns['raw_input'] = self.raw_input
150
150
151 def raw_input(self, prompt=''):
151 def raw_input(self, prompt=''):
152 """ A replacement from python's raw_input.
152 """ A replacement from python's raw_input.
153 """
153 """
154 self.new_prompt(prompt)
154 self.new_prompt(prompt)
155 self._input_state = 'raw_input'
155 self._input_state = 'raw_input'
156 if hasattr(self, '_cursor'):
156 if hasattr(self, '_cursor'):
157 del self._cursor
157 del self._cursor
158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
159 self.__old_on_enter = self._on_enter
159 self.__old_on_enter = self._on_enter
160 event_loop = wx.EventLoop()
160 event_loop = wx.EventLoop()
161 def my_on_enter():
161 def my_on_enter():
162 event_loop.Exit()
162 event_loop.Exit()
163 self._on_enter = my_on_enter
163 self._on_enter = my_on_enter
164 # XXX: Running a separate event_loop. Ugly.
164 # XXX: Running a separate event_loop. Ugly.
165 event_loop.Run()
165 event_loop.Run()
166 self._on_enter = self.__old_on_enter
166 self._on_enter = self.__old_on_enter
167 self._input_state = 'buffering'
167 self._input_state = 'buffering'
168 self._cursor = wx.BusyCursor()
168 self._cursor = wx.BusyCursor()
169 return self.input_buffer.rstrip('\n')
169 return self.input_buffer.rstrip('\n')
170
170
171
171
172 def system_call(self, command_string):
172 def system_call(self, command_string):
173 self._input_state = 'subprocess'
173 self._input_state = 'subprocess'
174 event_loop = wx.EventLoop()
174 event_loop = wx.EventLoop()
175 def _end_system_call():
175 def _end_system_call():
176 self._input_state = 'buffering'
176 self._input_state = 'buffering'
177 self._running_process = False
177 self._running_process = False
178 event_loop.Exit()
178 event_loop.Exit()
179
179
180 self._running_process = PipedProcess(command_string,
180 self._running_process = PipedProcess(command_string,
181 out_callback=self.buffered_write,
181 out_callback=self.buffered_write,
182 end_callback = _end_system_call)
182 end_callback = _end_system_call)
183 self._running_process.start()
183 self._running_process.start()
184 # XXX: Running a separate event_loop. Ugly.
184 # XXX: Running a separate event_loop. Ugly.
185 event_loop.Run()
185 event_loop.Run()
186 # Be sure to flush the buffer.
186 # Be sure to flush the buffer.
187 self._buffer_flush(event=None)
187 self._buffer_flush(event=None)
188
188
189
189
190 def do_calltip(self):
190 def do_calltip(self):
191 """ Analyse current and displays useful calltip for it.
191 """ Analyse current and displays useful calltip for it.
192 """
192 """
193 if self.debug:
193 if self.debug:
194 print >>sys.__stdout__, "do_calltip"
194 print >>sys.__stdout__, "do_calltip"
195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
196 symbol = self.input_buffer
196 symbol = self.input_buffer
197 symbol_string = separators.split(symbol)[-1]
197 symbol_string = separators.split(symbol)[-1]
198 base_symbol_string = symbol_string.split('.')[0]
198 base_symbol_string = symbol_string.split('.')[0]
199 if base_symbol_string in self.shell.user_ns:
199 if base_symbol_string in self.shell.user_ns:
200 symbol = self.shell.user_ns[base_symbol_string]
200 symbol = self.shell.user_ns[base_symbol_string]
201 elif base_symbol_string in self.shell.user_global_ns:
201 elif base_symbol_string in self.shell.user_global_ns:
202 symbol = self.shell.user_global_ns[base_symbol_string]
202 symbol = self.shell.user_global_ns[base_symbol_string]
203 elif base_symbol_string in __builtin__.__dict__:
203 elif base_symbol_string in __builtin__.__dict__:
204 symbol = __builtin__.__dict__[base_symbol_string]
204 symbol = __builtin__.__dict__[base_symbol_string]
205 else:
205 else:
206 return False
206 return False
207 try:
207 try:
208 for name in symbol_string.split('.')[1:] + ['__doc__']:
208 for name in symbol_string.split('.')[1:] + ['__doc__']:
209 symbol = getattr(symbol, name)
209 symbol = getattr(symbol, name)
210 self.AutoCompCancel()
210 self.AutoCompCancel()
211 # Check that the symbol can indeed be converted to a string:
211 # Check that the symbol can indeed be converted to a string:
212 symbol += ''
212 symbol += ''
213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
214 except:
214 except:
215 # The retrieve symbol couldn't be converted to a string
215 # The retrieve symbol couldn't be converted to a string
216 pass
216 pass
217
217
218
218
219 def _popup_completion(self, create=False):
219 def _popup_completion(self, create=False):
220 """ Updates the popup completion menu if it exists. If create is
220 """ Updates the popup completion menu if it exists. If create is
221 true, open the menu.
221 true, open the menu.
222 """
222 """
223 if self.debug:
223 if self.debug:
224 print >>sys.__stdout__, "_popup_completion"
224 print >>sys.__stdout__, "_popup_completion"
225 line = self.input_buffer
225 line = self.input_buffer
226 if (self.AutoCompActive() and line and not line[-1] == '.') \
226 if (self.AutoCompActive() and line and not line[-1] == '.') \
227 or create==True:
227 or create==True:
228 suggestion, completions = self.complete(line)
228 suggestion, completions = self.complete(line)
229 if completions:
229 if completions:
230 offset = len(self._get_completion_text(line))
230 offset = len(self._get_completion_text(line))
231 self.pop_completion(completions, offset=offset)
231 self.pop_completion(completions, offset=offset)
232 if self.debug:
232 if self.debug:
233 print >>sys.__stdout__, completions
233 print >>sys.__stdout__, completions
234
234
235
235
236 def buffered_write(self, text):
236 def buffered_write(self, text):
237 """ A write method for streams, that caches the stream in order
237 """ A write method for streams, that caches the stream in order
238 to avoid flooding the event loop.
238 to avoid flooding the event loop.
239
239
240 This can be called outside of the main loop, in separate
240 This can be called outside of the main loop, in separate
241 threads.
241 threads.
242 """
242 """
243 self._out_buffer_lock.acquire()
243 self._out_buffer_lock.acquire()
244 self._out_buffer.append(text)
244 self._out_buffer.append(text)
245 self._out_buffer_lock.release()
245 self._out_buffer_lock.release()
246 if not self._buffer_flush_timer.IsRunning():
246 if not self._buffer_flush_timer.IsRunning():
247 wx.CallAfter(self._buffer_flush_timer.Start,
247 wx.CallAfter(self._buffer_flush_timer.Start,
248 milliseconds=100, oneShot=True)
248 milliseconds=100, oneShot=True)
249
249
250
250
251 def clear_screen(self):
251 def clear_screen(self):
252 """ Empty completely the widget.
252 """ Empty completely the widget.
253 """
253 """
254 self.ClearAll()
254 self.ClearAll()
255 self.new_prompt(self.input_prompt_template.substitute(
255 self.new_prompt(self.input_prompt_template.substitute(
256 number=(self.last_result['number'] + 1)))
256 number=(self.last_result['number'] + 1)))
257
257
258
258
259 #--------------------------------------------------------------------------
259 #--------------------------------------------------------------------------
260 # LineFrontEnd interface
260 # LineFrontEnd interface
261 #--------------------------------------------------------------------------
261 #--------------------------------------------------------------------------
262
262
263 def execute(self, python_string, raw_string=None):
263 def execute(self, python_string, raw_string=None):
264 self._input_state = 'buffering'
264 self._input_state = 'buffering'
265 self.CallTipCancel()
265 self.CallTipCancel()
266 self._cursor = wx.BusyCursor()
266 self._cursor = wx.BusyCursor()
267 if raw_string is None:
267 if raw_string is None:
268 raw_string = python_string
268 raw_string = python_string
269 end_line = self.current_prompt_line \
269 end_line = self.current_prompt_line \
270 + max(1, len(raw_string.split('\n'))-1)
270 + max(1, len(raw_string.split('\n'))-1)
271 for i in range(self.current_prompt_line, end_line):
271 for i in range(self.current_prompt_line, end_line):
272 if i in self._markers:
272 if i in self._markers:
273 self.MarkerDeleteHandle(self._markers[i])
273 self.MarkerDeleteHandle(self._markers[i])
274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
275 # Use a callafter to update the display robustly under windows
275 # Use a callafter to update the display robustly under windows
276 def callback():
276 def callback():
277 self.GotoPos(self.GetLength())
277 self.GotoPos(self.GetLength())
278 PrefilterFrontEnd.execute(self, python_string,
278 PrefilterFrontEnd.execute(self, python_string,
279 raw_string=raw_string)
279 raw_string=raw_string)
280 wx.CallAfter(callback)
280 wx.CallAfter(callback)
281
281
282
282
283 def execute_command(self, command, hidden=False):
283 def execute_command(self, command, hidden=False):
284 """ Execute a command, not only in the model, but also in the
284 """ Execute a command, not only in the model, but also in the
285 view.
285 view.
286 """
286 """
287 # XXX: This method needs to be integrated in the base fronted
287 # XXX: This method needs to be integrated in the base fronted
288 # interface
288 # interface
289 if hidden:
289 if hidden:
290 return self.shell.execute(command)
290 return self.shell.execute(command)
291 else:
291 else:
292 # XXX: we are not storing the input buffer previous to the
292 # XXX: we are not storing the input buffer previous to the
293 # execution, as this forces us to run the execution
293 # execution, as this forces us to run the execution
294 # input_buffer a yield, which is not good.
294 # input_buffer a yield, which is not good.
295 ##current_buffer = self.shell.control.input_buffer
295 ##current_buffer = self.shell.control.input_buffer
296 command = command.rstrip()
296 command = command.rstrip()
297 if len(command.split('\n')) > 1:
297 if len(command.split('\n')) > 1:
298 # The input command is several lines long, we need to
298 # The input command is several lines long, we need to
299 # force the execution to happen
299 # force the execution to happen
300 command += '\n'
300 command += '\n'
301 cleaned_command = self.prefilter_input(command)
301 cleaned_command = self.prefilter_input(command)
302 self.input_buffer = command
302 self.input_buffer = command
303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
304 # recursive yields.
304 # recursive yields.
305 self.ProcessEvent(wx.PaintEvent())
305 self.ProcessEvent(wx.PaintEvent())
306 self.write('\n')
306 self.write('\n')
307 if not self.is_complete(cleaned_command + '\n'):
307 if not self.is_complete(cleaned_command + '\n'):
308 self._colorize_input_buffer()
308 self._colorize_input_buffer()
309 self.render_error('Incomplete or invalid input')
309 self.render_error('Incomplete or invalid input')
310 self.new_prompt(self.input_prompt_template.substitute(
310 self.new_prompt(self.input_prompt_template.substitute(
311 number=(self.last_result['number'] + 1)))
311 number=(self.last_result['number'] + 1)))
312 return False
312 return False
313 self._on_enter()
313 self._on_enter()
314 return True
314 return True
315
315
316
316
317 def save_output_hooks(self):
317 def save_output_hooks(self):
318 self.__old_raw_input = __builtin__.raw_input
318 self.__old_raw_input = __builtin__.raw_input
319 PrefilterFrontEnd.save_output_hooks(self)
319 PrefilterFrontEnd.save_output_hooks(self)
320
320
321 def capture_output(self):
321 def capture_output(self):
322 self.SetLexer(stc.STC_LEX_NULL)
322 self.SetLexer(stc.STC_LEX_NULL)
323 PrefilterFrontEnd.capture_output(self)
323 PrefilterFrontEnd.capture_output(self)
324 __builtin__.raw_input = self.raw_input
324 __builtin__.raw_input = self.raw_input
325
325
326
326
327 def release_output(self):
327 def release_output(self):
328 __builtin__.raw_input = self.__old_raw_input
328 __builtin__.raw_input = self.__old_raw_input
329 PrefilterFrontEnd.release_output(self)
329 PrefilterFrontEnd.release_output(self)
330 self.SetLexer(stc.STC_LEX_PYTHON)
330 self.SetLexer(stc.STC_LEX_PYTHON)
331
331
332
332
333 def after_execute(self):
333 def after_execute(self):
334 PrefilterFrontEnd.after_execute(self)
334 PrefilterFrontEnd.after_execute(self)
335 # Clear the wait cursor
335 # Clear the wait cursor
336 if hasattr(self, '_cursor'):
336 if hasattr(self, '_cursor'):
337 del self._cursor
337 del self._cursor
338 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
338 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
339
339
340
340
341 def show_traceback(self):
341 def show_traceback(self):
342 start_line = self.GetCurrentLine()
342 start_line = self.GetCurrentLine()
343 PrefilterFrontEnd.show_traceback(self)
343 PrefilterFrontEnd.show_traceback(self)
344 self.ProcessEvent(wx.PaintEvent())
344 self.ProcessEvent(wx.PaintEvent())
345 #wx.Yield()
345 #wx.Yield()
346 for i in range(start_line, self.GetCurrentLine()):
346 for i in range(start_line, self.GetCurrentLine()):
347 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
347 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
348
348
349
349
350 #--------------------------------------------------------------------------
350 #--------------------------------------------------------------------------
351 # FrontEndBase interface
351 # FrontEndBase interface
352 #--------------------------------------------------------------------------
352 #--------------------------------------------------------------------------
353
353
354 def render_error(self, e):
354 def render_error(self, e):
355 start_line = self.GetCurrentLine()
355 start_line = self.GetCurrentLine()
356 self.write('\n' + e + '\n')
356 self.write('\n' + e + '\n')
357 for i in range(start_line, self.GetCurrentLine()):
357 for i in range(start_line, self.GetCurrentLine()):
358 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
358 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
359
359
360
360
361 #--------------------------------------------------------------------------
361 #--------------------------------------------------------------------------
362 # ConsoleWidget interface
362 # ConsoleWidget interface
363 #--------------------------------------------------------------------------
363 #--------------------------------------------------------------------------
364
364
365 def new_prompt(self, prompt):
365 def new_prompt(self, prompt):
366 """ Display a new prompt, and start a new input buffer.
366 """ Display a new prompt, and start a new input buffer.
367 """
367 """
368 self._input_state = 'readline'
368 self._input_state = 'readline'
369 ConsoleWidget.new_prompt(self, prompt)
369 ConsoleWidget.new_prompt(self, prompt)
370 i = self.current_prompt_line
370 i = self.current_prompt_line
371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
372
372
373
373
374 def continuation_prompt(self, *args, **kwargs):
374 def continuation_prompt(self, *args, **kwargs):
375 # Avoid multiple inheritence, be explicit about which
375 # Avoid multiple inheritence, be explicit about which
376 # parent method class gets called
376 # parent method class gets called
377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
378
378
379
379
380 def write(self, *args, **kwargs):
380 def write(self, *args, **kwargs):
381 # Avoid multiple inheritence, be explicit about which
381 # Avoid multiple inheritence, be explicit about which
382 # parent method class gets called
382 # parent method class gets called
383 return ConsoleWidget.write(self, *args, **kwargs)
383 return ConsoleWidget.write(self, *args, **kwargs)
384
384
385
385
386 def _on_key_down(self, event, skip=True):
386 def _on_key_down(self, event, skip=True):
387 """ Capture the character events, let the parent
387 """ Capture the character events, let the parent
388 widget handle them, and put our logic afterward.
388 widget handle them, and put our logic afterward.
389 """
389 """
390 # FIXME: This method needs to be broken down in smaller ones.
390 # FIXME: This method needs to be broken down in smaller ones.
391 current_line_num = self.GetCurrentLine()
391 current_line_num = self.GetCurrentLine()
392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
392 key_code = event.GetKeyCode()
393 if key_code in (ord('c'), ord('C')) and event.ControlDown():
393 # Capture Control-C
394 # Capture Control-C
394 if self._input_state == 'subprocess':
395 if self._input_state == 'subprocess':
395 if self.debug:
396 if self.debug:
396 print >>sys.__stderr__, 'Killing running process'
397 print >>sys.__stderr__, 'Killing running process'
397 if hasattr(self._running_process, 'process'):
398 if hasattr(self._running_process, 'process'):
398 self._running_process.process.kill()
399 self._running_process.process.kill()
399 elif self._input_state == 'buffering':
400 elif self._input_state == 'buffering':
400 if self.debug:
401 if self.debug:
401 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
402 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
402 raise KeyboardInterrupt
403 raise KeyboardInterrupt
403 # XXX: We need to make really sure we
404 # XXX: We need to make really sure we
404 # get back to a prompt.
405 # get back to a prompt.
405 elif self._input_state == 'subprocess' and (
406 elif self._input_state == 'subprocess' and (
406 ( event.KeyCode<256 and
407 ( key_code <256 and not event.ControlDown() )
407 not event.ControlDown() )
408 or
408 or
409 ( event.KeyCode in (ord('d'), ord('D')) and
409 ( key_code in (ord('d'), ord('D')) and
410 event.ControlDown())):
410 event.ControlDown())):
411 # We are running a process, we redirect keys.
411 # We are running a process, we redirect keys.
412 ConsoleWidget._on_key_down(self, event, skip=skip)
412 ConsoleWidget._on_key_down(self, event, skip=skip)
413 char = chr(event.KeyCode)
413 char = chr(key_code)
414 # Deal with some inconsistency in wx keycodes:
414 # Deal with some inconsistency in wx keycodes:
415 if char == '\r':
415 if char == '\r':
416 char = '\n'
416 char = '\n'
417 elif not event.ShiftDown():
417 elif not event.ShiftDown():
418 char = char.lower()
418 char = char.lower()
419 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
419 if event.ControlDown() and key_code in (ord('d'), ord('D')):
420 char = '\04'
420 char = '\04'
421 self._running_process.process.stdin.write(char)
421 self._running_process.process.stdin.write(char)
422 self._running_process.process.stdin.flush()
422 self._running_process.process.stdin.flush()
423 elif event.KeyCode in (ord('('), 57, 53):
423 elif key_code in (ord('('), 57, 53):
424 # Calltips
424 # Calltips
425 event.Skip()
425 event.Skip()
426 self.do_calltip()
426 self.do_calltip()
427 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
427 elif self.AutoCompActive() and not key_code == ord('\t'):
428 event.Skip()
428 event.Skip()
429 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
429 if key_code in (wx.WXK_BACK, wx.WXK_DELETE):
430 wx.CallAfter(self._popup_completion, create=True)
430 wx.CallAfter(self._popup_completion, create=True)
431 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
431 elif not key_code in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
432 wx.WXK_RIGHT, wx.WXK_ESCAPE):
432 wx.WXK_RIGHT, wx.WXK_ESCAPE):
433 wx.CallAfter(self._popup_completion)
433 wx.CallAfter(self._popup_completion)
434 else:
434 else:
435 # Up history
435 # Up history
436 if event.KeyCode == wx.WXK_UP and (
436 if key_code == wx.WXK_UP and (
437 ( current_line_num == self.current_prompt_line and
437 event.ControlDown() or
438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
438 current_line_num == self.current_prompt_line
439 or event.ControlDown() ):
439 ):
440 new_buffer = self.get_history_previous(
440 new_buffer = self.get_history_previous(
441 self.input_buffer)
441 self.input_buffer)
442 if new_buffer is not None:
442 if new_buffer is not None:
443 self.input_buffer = new_buffer
443 self.input_buffer = new_buffer
444 if self.GetCurrentLine() > self.current_prompt_line:
444 if self.GetCurrentLine() > self.current_prompt_line:
445 # Go to first line, for seemless history up.
445 # Go to first line, for seemless history up.
446 self.GotoPos(self.current_prompt_pos)
446 self.GotoPos(self.current_prompt_pos)
447 # Down history
447 # Down history
448 elif event.KeyCode == wx.WXK_DOWN and (
448 elif key_code == wx.WXK_DOWN and (
449 ( current_line_num == self.LineCount -1 and
449 event.ControlDown() or
450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
450 current_line_num == self.LineCount -1
451 or event.ControlDown() ):
451 ):
452 new_buffer = self.get_history_next()
452 new_buffer = self.get_history_next()
453 if new_buffer is not None:
453 if new_buffer is not None:
454 self.input_buffer = new_buffer
454 self.input_buffer = new_buffer
455 # Tab-completion
455 # Tab-completion
456 elif event.KeyCode == ord('\t'):
456 elif key_code == ord('\t'):
457 current_line, current_line_num = self.CurLine
457 current_line, current_line_num = self.CurLine
458 if not re.match(r'^\s*$', current_line):
458 if not re.match(r'^%s\s*$' % self.continuation_prompt(),
459 current_line):
459 self.complete_current_input()
460 self.complete_current_input()
460 if self.AutoCompActive():
461 if self.AutoCompActive():
461 wx.CallAfter(self._popup_completion, create=True)
462 wx.CallAfter(self._popup_completion, create=True)
462 else:
463 else:
463 event.Skip()
464 event.Skip()
464 elif event.KeyCode == wx.WXK_BACK:
465 elif key_code == wx.WXK_BACK:
465 # If characters where erased, check if we have to
466 # If characters where erased, check if we have to
466 # remove a line.
467 # remove a line.
467 # XXX: What about DEL?
468 # XXX: What about DEL?
468 # FIXME: This logics should be in ConsoleWidget, as it is
469 # FIXME: This logics should be in ConsoleWidget, as it is
469 # independant of IPython
470 # independant of IPython
470 current_line, _ = self.CurLine
471 current_line, _ = self.CurLine
471 current_pos = self.GetCurrentPos()
472 current_pos = self.GetCurrentPos()
472 current_line_num = self.LineFromPosition(current_pos)
473 current_line_num = self.LineFromPosition(current_pos)
473 current_col = self.GetColumn(current_pos)
474 current_col = self.GetColumn(current_pos)
474 len_prompt = len(self.continuation_prompt())
475 len_prompt = len(self.continuation_prompt())
475 if ( current_line.startswith(self.continuation_prompt())
476 if ( current_line.startswith(self.continuation_prompt())
476 and current_col == len_prompt):
477 and current_col == len_prompt):
477 new_lines = []
478 new_lines = []
478 for line_num, line in enumerate(
479 for line_num, line in enumerate(
479 self.input_buffer.split('\n')):
480 self.input_buffer.split('\n')):
480 if (line_num + self.current_prompt_line ==
481 if (line_num + self.current_prompt_line ==
481 current_line_num):
482 current_line_num):
482 new_lines.append(line[len_prompt:])
483 new_lines.append(line[len_prompt:])
483 else:
484 else:
484 new_lines.append('\n'+line)
485 new_lines.append('\n'+line)
485 # The first character is '\n', due to the above
486 # The first character is '\n', due to the above
486 # code:
487 # code:
487 self.input_buffer = ''.join(new_lines)[1:]
488 self.input_buffer = ''.join(new_lines)[1:]
488 self.GotoPos(current_pos - 1 - len_prompt)
489 self.GotoPos(current_pos - 1 - len_prompt)
489 else:
490 else:
490 ConsoleWidget._on_key_down(self, event, skip=skip)
491 ConsoleWidget._on_key_down(self, event, skip=skip)
491 else:
492 else:
492 ConsoleWidget._on_key_down(self, event, skip=skip)
493 ConsoleWidget._on_key_down(self, event, skip=skip)
493
494
494
495
495
496
496 def _on_key_up(self, event, skip=True):
497 def _on_key_up(self, event, skip=True):
497 """ Called when any key is released.
498 """ Called when any key is released.
498 """
499 """
499 if event.KeyCode in (59, ord('.')):
500 if event.GetKeyCode() in (59, ord('.')):
500 # Intercepting '.'
501 # Intercepting '.'
501 event.Skip()
502 event.Skip()
502 wx.CallAfter(self._popup_completion, create=True)
503 wx.CallAfter(self._popup_completion, create=True)
503 else:
504 else:
504 ConsoleWidget._on_key_up(self, event, skip=skip)
505 ConsoleWidget._on_key_up(self, event, skip=skip)
505 # Make sure the continuation_prompts are always followed by a
506 # Make sure the continuation_prompts are always followed by a
506 # whitespace
507 # whitespace
507 new_lines = []
508 new_lines = []
508 if self._input_state == 'readline':
509 if self._input_state == 'readline':
509 position = self.GetCurrentPos()
510 position = self.GetCurrentPos()
510 continuation_prompt = self.continuation_prompt()[:-1]
511 continuation_prompt = self.continuation_prompt()[:-1]
511 for line in self.input_buffer.split('\n'):
512 for line in self.input_buffer.split('\n'):
512 if not line == continuation_prompt:
513 if not line == continuation_prompt:
513 new_lines.append(line)
514 new_lines.append(line)
514 self.input_buffer = '\n'.join(new_lines)
515 self.input_buffer = '\n'.join(new_lines)
515 self.GotoPos(position)
516 self.GotoPos(position)
516
517
517
518
518 def _on_enter(self):
519 def _on_enter(self):
519 """ Called on return key down, in readline input_state.
520 """ Called on return key down, in readline input_state.
520 """
521 """
521 last_line_num = self.LineFromPosition(self.GetLength())
522 last_line_num = self.LineFromPosition(self.GetLength())
522 current_line_num = self.LineFromPosition(self.GetCurrentPos())
523 current_line_num = self.LineFromPosition(self.GetCurrentPos())
523 new_line_pos = (last_line_num - current_line_num)
524 new_line_pos = (last_line_num - current_line_num)
524 if self.debug:
525 if self.debug:
525 print >>sys.__stdout__, repr(self.input_buffer)
526 print >>sys.__stdout__, repr(self.input_buffer)
526 self.write('\n', refresh=False)
527 self.write('\n', refresh=False)
527 # Under windows scintilla seems to be doing funny
528 # Under windows scintilla seems to be doing funny
528 # stuff to the line returns here, but the getter for
529 # stuff to the line returns here, but the getter for
529 # input_buffer filters this out.
530 # input_buffer filters this out.
530 if sys.platform == 'win32':
531 if sys.platform == 'win32':
531 self.input_buffer = self.input_buffer
532 self.input_buffer = self.input_buffer
532 old_prompt_num = self.current_prompt_pos
533 old_prompt_num = self.current_prompt_pos
533 has_executed = PrefilterFrontEnd._on_enter(self,
534 has_executed = PrefilterFrontEnd._on_enter(self,
534 new_line_pos=new_line_pos)
535 new_line_pos=new_line_pos)
535 if old_prompt_num == self.current_prompt_pos:
536 if old_prompt_num == self.current_prompt_pos:
536 # No execution has happened
537 # No execution has happened
537 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
538 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
538 return has_executed
539 return has_executed
539
540
540
541
541 #--------------------------------------------------------------------------
542 #--------------------------------------------------------------------------
542 # EditWindow API
543 # EditWindow API
543 #--------------------------------------------------------------------------
544 #--------------------------------------------------------------------------
544
545
545 def OnUpdateUI(self, event):
546 def OnUpdateUI(self, event):
546 """ Override the OnUpdateUI of the EditWindow class, to prevent
547 """ Override the OnUpdateUI of the EditWindow class, to prevent
547 syntax highlighting both for faster redraw, and for more
548 syntax highlighting both for faster redraw, and for more
548 consistent look and feel.
549 consistent look and feel.
549 """
550 """
550 if not self._input_state == 'readline':
551 if not self._input_state == 'readline':
551 ConsoleWidget.OnUpdateUI(self, event)
552 ConsoleWidget.OnUpdateUI(self, event)
552
553
553 #--------------------------------------------------------------------------
554 #--------------------------------------------------------------------------
554 # Private API
555 # Private API
555 #--------------------------------------------------------------------------
556 #--------------------------------------------------------------------------
556
557
557 def _buffer_flush(self, event):
558 def _buffer_flush(self, event):
558 """ Called by the timer to flush the write buffer.
559 """ Called by the timer to flush the write buffer.
559
560
560 This is always called in the mainloop, by the wx timer.
561 This is always called in the mainloop, by the wx timer.
561 """
562 """
562 self._out_buffer_lock.acquire()
563 self._out_buffer_lock.acquire()
563 _out_buffer = self._out_buffer
564 _out_buffer = self._out_buffer
564 self._out_buffer = []
565 self._out_buffer = []
565 self._out_buffer_lock.release()
566 self._out_buffer_lock.release()
566 self.write(''.join(_out_buffer), refresh=False)
567 self.write(''.join(_out_buffer), refresh=False)
567
568
568
569
569 def _colorize_input_buffer(self):
570 def _colorize_input_buffer(self):
570 """ Keep the input buffer lines at a bright color.
571 """ Keep the input buffer lines at a bright color.
571 """
572 """
572 if not self._input_state in ('readline', 'raw_input'):
573 if not self._input_state in ('readline', 'raw_input'):
573 return
574 return
574 end_line = self.GetCurrentLine()
575 end_line = self.GetCurrentLine()
575 if not sys.platform == 'win32':
576 if not sys.platform == 'win32':
576 end_line += 1
577 end_line += 1
577 for i in range(self.current_prompt_line, end_line):
578 for i in range(self.current_prompt_line, end_line):
578 if i in self._markers:
579 if i in self._markers:
579 self.MarkerDeleteHandle(self._markers[i])
580 self.MarkerDeleteHandle(self._markers[i])
580 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
581 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
581
582
582
583
583 if __name__ == '__main__':
584 if __name__ == '__main__':
584 class MainWindow(wx.Frame):
585 class MainWindow(wx.Frame):
585 def __init__(self, parent, id, title):
586 def __init__(self, parent, id, title):
586 wx.Frame.__init__(self, parent, id, title, size=(300,250))
587 wx.Frame.__init__(self, parent, id, title, size=(300,250))
587 self._sizer = wx.BoxSizer(wx.VERTICAL)
588 self._sizer = wx.BoxSizer(wx.VERTICAL)
588 self.shell = WxController(self)
589 self.shell = WxController(self)
589 self._sizer.Add(self.shell, 1, wx.EXPAND)
590 self._sizer.Add(self.shell, 1, wx.EXPAND)
590 self.SetSizer(self._sizer)
591 self.SetSizer(self._sizer)
591 self.SetAutoLayout(1)
592 self.SetAutoLayout(1)
592 self.Show(True)
593 self.Show(True)
593
594
594 app = wx.PySimpleApp()
595 app = wx.PySimpleApp()
595 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
596 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
596 frame.shell.SetFocus()
597 frame.shell.SetFocus()
597 frame.SetSize((680, 460))
598 frame.SetSize((680, 460))
598 self = frame.shell
599 self = frame.shell
599
600
600 app.MainLoop()
601 app.MainLoop()
601
602
1 NO CONTENT: modified file chmod 100755 => 100644
NO CONTENT: modified file chmod 100755 => 100644
General Comments 0
You need to be logged in to leave comments. Login now