##// END OF EJS Templates
ENH: Backspace now deletes continuation lines
Gael Varoquaux -
Show More
@@ -1,645 +1,644 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
70
71 # new style numbers
71 # new style numbers
72 _STDOUT_STYLE = 15
72 _STDOUT_STYLE = 15
73 _STDERR_STYLE = 16
73 _STDERR_STYLE = 16
74 _TRACE_STYLE = 17
74 _TRACE_STYLE = 17
75
75
76
76
77 # system colors
77 # system colors
78 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
78 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
79
79
80 # Translation table from ANSI escape sequences to color.
80 # Translation table from ANSI escape sequences to color.
81 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
81 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
82 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
82 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
83 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
83 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
84 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
84 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
85 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
85 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
86 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
86 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
87 '1;34': [12, 'LIGHT BLUE'], '1;35':
87 '1;34': [12, 'LIGHT BLUE'], '1;35':
88 [13, 'MEDIUM VIOLET RED'],
88 [13, 'MEDIUM VIOLET RED'],
89 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
89 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
90
90
91 #we define platform specific fonts
91 #we define platform specific fonts
92 if wx.Platform == '__WXMSW__':
92 if wx.Platform == '__WXMSW__':
93 FACES = { 'times': 'Times New Roman',
93 FACES = { 'times': 'Times New Roman',
94 'mono' : 'Courier New',
94 'mono' : 'Courier New',
95 'helv' : 'Arial',
95 'helv' : 'Arial',
96 'other': 'Comic Sans MS',
96 'other': 'Comic Sans MS',
97 'size' : 10,
97 'size' : 10,
98 'size2': 8,
98 'size2': 8,
99 }
99 }
100 elif wx.Platform == '__WXMAC__':
100 elif wx.Platform == '__WXMAC__':
101 FACES = { 'times': 'Times New Roman',
101 FACES = { 'times': 'Times New Roman',
102 'mono' : 'Monaco',
102 'mono' : 'Monaco',
103 'helv' : 'Arial',
103 'helv' : 'Arial',
104 'other': 'Comic Sans MS',
104 'other': 'Comic Sans MS',
105 'size' : 10,
105 'size' : 10,
106 'size2': 8,
106 'size2': 8,
107 }
107 }
108 else:
108 else:
109 FACES = { 'times': 'Times',
109 FACES = { 'times': 'Times',
110 'mono' : 'Courier',
110 'mono' : 'Courier',
111 'helv' : 'Helvetica',
111 'helv' : 'Helvetica',
112 'other': 'new century schoolbook',
112 'other': 'new century schoolbook',
113 'size' : 10,
113 'size' : 10,
114 'size2': 8,
114 'size2': 8,
115 }
115 }
116
116
117
117
118 #-------------------------------------------------------------------------------
118 #-------------------------------------------------------------------------------
119 # The console widget class
119 # The console widget class
120 #-------------------------------------------------------------------------------
120 #-------------------------------------------------------------------------------
121 class ConsoleWidget(editwindow.EditWindow):
121 class ConsoleWidget(editwindow.EditWindow):
122 """ Specialized styled text control view for console-like workflow.
122 """ Specialized styled text control view for console-like workflow.
123
123
124 This widget is mainly interested in dealing with the prompt and
124 This widget is mainly interested in dealing with the prompt and
125 keeping the cursor inside the editing line.
125 keeping the cursor inside the editing line.
126 """
126 """
127
127
128 # This is where the title captured from the ANSI escape sequences are
128 # This is where the title captured from the ANSI escape sequences are
129 # stored.
129 # stored.
130 title = 'Console'
130 title = 'Console'
131
131
132 # Last prompt printed
132 # Last prompt printed
133 last_prompt = ''
133 last_prompt = ''
134
134
135 # The buffer being edited.
135 # The buffer being edited.
136 def _set_input_buffer(self, string):
136 def _set_input_buffer(self, string):
137 self.SetSelection(self.current_prompt_pos, self.GetLength())
137 self.SetSelection(self.current_prompt_pos, self.GetLength())
138 self.ReplaceSelection(string)
138 self.ReplaceSelection(string)
139 self.GotoPos(self.GetLength())
139 self.GotoPos(self.GetLength())
140
140
141 def _get_input_buffer(self):
141 def _get_input_buffer(self):
142 """ Returns the text in current edit buffer.
142 """ Returns the text in current edit buffer.
143 """
143 """
144 input_buffer = self.GetTextRange(self.current_prompt_pos,
144 input_buffer = self.GetTextRange(self.current_prompt_pos,
145 self.GetLength())
145 self.GetLength())
146 input_buffer = input_buffer.replace(LINESEP, '\n')
146 input_buffer = input_buffer.replace(LINESEP, '\n')
147 return input_buffer
147 return input_buffer
148
148
149 input_buffer = property(_get_input_buffer, _set_input_buffer)
149 input_buffer = property(_get_input_buffer, _set_input_buffer)
150
150
151 style = _DEFAULT_STYLE.copy()
151 style = _DEFAULT_STYLE.copy()
152
152
153 # Translation table from ANSI escape sequences to color. Override
153 # Translation table from ANSI escape sequences to color. Override
154 # this to specify your colors.
154 # this to specify your colors.
155 ANSI_STYLES = ANSI_STYLES.copy()
155 ANSI_STYLES = ANSI_STYLES.copy()
156
156
157 # Font faces
157 # Font faces
158 faces = FACES.copy()
158 faces = FACES.copy()
159
159
160 # Store the last time a refresh was done
160 # Store the last time a refresh was done
161 _last_refresh_time = 0
161 _last_refresh_time = 0
162
162
163 #--------------------------------------------------------------------------
163 #--------------------------------------------------------------------------
164 # Public API
164 # Public API
165 #--------------------------------------------------------------------------
165 #--------------------------------------------------------------------------
166
166
167 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
167 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
168 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
168 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
169 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
169 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
170 self.configure_scintilla()
170 self.configure_scintilla()
171 # Track if 'enter' key as ever been processed
171 # Track if 'enter' key as ever been processed
172 # This variable will only be reallowed until key goes up
172 # This variable will only be reallowed until key goes up
173 self.enter_catched = False
173 self.enter_catched = False
174 self.current_prompt_pos = 0
174 self.current_prompt_pos = 0
175
175
176 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
176 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
177 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
177 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
178
178
179
179
180 def write(self, text, refresh=True):
180 def write(self, text, refresh=True):
181 """ Write given text to buffer, while translating the ansi escape
181 """ Write given text to buffer, while translating the ansi escape
182 sequences.
182 sequences.
183 """
183 """
184 # XXX: do not put print statements to sys.stdout/sys.stderr in
184 # XXX: do not put print statements to sys.stdout/sys.stderr in
185 # this method, the print statements will call this method, as
185 # this method, the print statements will call this method, as
186 # you will end up with an infinit loop
186 # you will end up with an infinit loop
187 title = self.title_pat.split(text)
187 title = self.title_pat.split(text)
188 if len(title)>1:
188 if len(title)>1:
189 self.title = title[-2]
189 self.title = title[-2]
190
190
191 text = self.title_pat.sub('', text)
191 text = self.title_pat.sub('', text)
192 segments = self.color_pat.split(text)
192 segments = self.color_pat.split(text)
193 segment = segments.pop(0)
193 segment = segments.pop(0)
194 self.GotoPos(self.GetLength())
194 self.GotoPos(self.GetLength())
195 self.StartStyling(self.GetLength(), 0xFF)
195 self.StartStyling(self.GetLength(), 0xFF)
196 try:
196 try:
197 self.AppendText(segment)
197 self.AppendText(segment)
198 except UnicodeDecodeError:
198 except UnicodeDecodeError:
199 # XXX: Do I really want to skip the exception?
199 # XXX: Do I really want to skip the exception?
200 pass
200 pass
201
201
202 if segments:
202 if segments:
203 for ansi_tag, text in zip(segments[::2], segments[1::2]):
203 for ansi_tag, text in zip(segments[::2], segments[1::2]):
204 self.StartStyling(self.GetLength(), 0xFF)
204 self.StartStyling(self.GetLength(), 0xFF)
205 try:
205 try:
206 self.AppendText(text)
206 self.AppendText(text)
207 except UnicodeDecodeError:
207 except UnicodeDecodeError:
208 # XXX: Do I really want to skip the exception?
208 # XXX: Do I really want to skip the exception?
209 pass
209 pass
210
210
211 if ansi_tag not in self.ANSI_STYLES:
211 if ansi_tag not in self.ANSI_STYLES:
212 style = 0
212 style = 0
213 else:
213 else:
214 style = self.ANSI_STYLES[ansi_tag][0]
214 style = self.ANSI_STYLES[ansi_tag][0]
215
215
216 self.SetStyling(len(text), style)
216 self.SetStyling(len(text), style)
217
217
218 self.GotoPos(self.GetLength())
218 self.GotoPos(self.GetLength())
219 if refresh:
219 if refresh:
220 current_time = time.time()
220 current_time = time.time()
221 if current_time - self._last_refresh_time > 0.03:
221 if current_time - self._last_refresh_time > 0.03:
222 if sys.platform == 'win32':
222 if sys.platform == 'win32':
223 wx.SafeYield()
223 wx.SafeYield()
224 else:
224 else:
225 wx.Yield()
225 wx.Yield()
226 # self.ProcessEvent(wx.PaintEvent())
226 # self.ProcessEvent(wx.PaintEvent())
227 self._last_refresh_time = current_time
227 self._last_refresh_time = current_time
228
228
229
229
230 def new_prompt(self, prompt):
230 def new_prompt(self, prompt):
231 """ Prints a prompt at start of line, and move the start of the
231 """ Prints a prompt at start of line, and move the start of the
232 current block there.
232 current block there.
233
233
234 The prompt can be given with ascii escape sequences.
234 The prompt can be given with ascii escape sequences.
235 """
235 """
236 self.write(prompt, refresh=False)
236 self.write(prompt, refresh=False)
237 # now we update our cursor giving end of prompt
237 # now we update our cursor giving end of prompt
238 self.current_prompt_pos = self.GetLength()
238 self.current_prompt_pos = self.GetLength()
239 self.current_prompt_line = self.GetCurrentLine()
239 self.current_prompt_line = self.GetCurrentLine()
240 self.EnsureCaretVisible()
240 self.EnsureCaretVisible()
241 self.last_prompt = prompt
241 self.last_prompt = prompt
242
242
243
243
244 def continuation_prompt(self):
244 def continuation_prompt(self):
245 """Returns the current continuation prompt.
245 """ Returns the current continuation prompt.
246 We need to implement this method here to deal with the
247 ascii escape sequences cleaning up.
246 """
248 """
247 # ASCII-less prompt
249 # ASCII-less prompt
248 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
250 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
249 return "."*(len(ascii_less)-2) + ': '
251 return "."*(len(ascii_less)-2) + ': '
250
252
251
253
252 def scroll_to_bottom(self):
254 def scroll_to_bottom(self):
253 maxrange = self.GetScrollRange(wx.VERTICAL)
255 maxrange = self.GetScrollRange(wx.VERTICAL)
254 self.ScrollLines(maxrange)
256 self.ScrollLines(maxrange)
255
257
256
258
257 def pop_completion(self, possibilities, offset=0):
259 def pop_completion(self, possibilities, offset=0):
258 """ Pops up an autocompletion menu. Offset is the offset
260 """ Pops up an autocompletion menu. Offset is the offset
259 in characters of the position at which the menu should
261 in characters of the position at which the menu should
260 appear, relativ to the cursor.
262 appear, relativ to the cursor.
261 """
263 """
262 self.AutoCompSetIgnoreCase(False)
264 self.AutoCompSetIgnoreCase(False)
263 self.AutoCompSetAutoHide(False)
265 self.AutoCompSetAutoHide(False)
264 self.AutoCompSetMaxHeight(len(possibilities))
266 self.AutoCompSetMaxHeight(len(possibilities))
265 self.AutoCompShow(offset, " ".join(possibilities))
267 self.AutoCompShow(offset, " ".join(possibilities))
266
268
267
269
268 def get_line_width(self):
270 def get_line_width(self):
269 """ Return the width of the line in characters.
271 """ Return the width of the line in characters.
270 """
272 """
271 return self.GetSize()[0]/self.GetCharWidth()
273 return self.GetSize()[0]/self.GetCharWidth()
272
274
273
275
274
275 #--------------------------------------------------------------------------
276 # EditWindow API
277 #--------------------------------------------------------------------------
278
279 def OnUpdateUI(self, event):
280 """ Override the OnUpdateUI of the EditWindow class, to prevent
281 syntax highlighting both for faster redraw, and for more
282 consistent look and feel.
283 """
284
285 #--------------------------------------------------------------------------
286 # Styling API
287 #--------------------------------------------------------------------------
288
289 def configure_scintilla(self):
276 def configure_scintilla(self):
290
277
291 p = self.style
278 p = self.style
292
279
293 #First we define the special background colors
280 #First we define the special background colors
294 if 'trace' in p:
281 if 'trace' in p:
295 _COMPLETE_BUFFER_BG = p['trace']
282 _COMPLETE_BUFFER_BG = p['trace']
296 else:
283 else:
297 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
284 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
298
285
299 if 'stdout' in p:
286 if 'stdout' in p:
300 _INPUT_BUFFER_BG = p['stdout']
287 _INPUT_BUFFER_BG = p['stdout']
301 else:
288 else:
302 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
289 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
303
290
304 if 'stderr' in p:
291 if 'stderr' in p:
305 _ERROR_BG = p['stderr']
292 _ERROR_BG = p['stderr']
306 else:
293 else:
307 _ERROR_BG = '#FFF1F1' # Nice red
294 _ERROR_BG = '#FFF1F1' # Nice red
308
295
309 # Marker for complete buffer.
296 # Marker for complete buffer.
310 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
297 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
311 background = _COMPLETE_BUFFER_BG)
298 background = _COMPLETE_BUFFER_BG)
312
299
313 # Marker for current input buffer.
300 # Marker for current input buffer.
314 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
301 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
315 background = _INPUT_BUFFER_BG)
302 background = _INPUT_BUFFER_BG)
316 # Marker for tracebacks.
303 # Marker for tracebacks.
317 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
304 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
318 background = _ERROR_BG)
305 background = _ERROR_BG)
319
306
320 self.SetEOLMode(stc.STC_EOL_LF)
307 self.SetEOLMode(stc.STC_EOL_LF)
321
308
322 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
309 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
323 # the widget
310 # the widget
324 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
311 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
325 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
312 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
326 # Also allow Ctrl Shift "=" for poor non US keyboard users.
313 # Also allow Ctrl Shift "=" for poor non US keyboard users.
327 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
314 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
328 stc.STC_CMD_ZOOMIN)
315 stc.STC_CMD_ZOOMIN)
329
316
330 # Keys: we need to clear some of the keys the that don't play
317 # Keys: we need to clear some of the keys the that don't play
331 # well with a console.
318 # well with a console.
332 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
319 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
333 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
320 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
334 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
321 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
335 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
322 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
336
323
337 self.SetEOLMode(stc.STC_EOL_CRLF)
324 self.SetEOLMode(stc.STC_EOL_CRLF)
338 self.SetWrapMode(stc.STC_WRAP_CHAR)
325 self.SetWrapMode(stc.STC_WRAP_CHAR)
339 self.SetWrapMode(stc.STC_WRAP_WORD)
326 self.SetWrapMode(stc.STC_WRAP_WORD)
340 self.SetBufferedDraw(True)
327 self.SetBufferedDraw(True)
341
328
342 if 'antialiasing' in p:
329 if 'antialiasing' in p:
343 self.SetUseAntiAliasing(p['antialiasing'])
330 self.SetUseAntiAliasing(p['antialiasing'])
344 else:
331 else:
345 self.SetUseAntiAliasing(True)
332 self.SetUseAntiAliasing(True)
346
333
347 self.SetLayoutCache(stc.STC_CACHE_PAGE)
334 self.SetLayoutCache(stc.STC_CACHE_PAGE)
348 self.SetUndoCollection(False)
335 self.SetUndoCollection(False)
349 self.SetUseTabs(True)
336 self.SetUseTabs(True)
350 self.SetIndent(4)
337 self.SetIndent(4)
351 self.SetTabWidth(4)
338 self.SetTabWidth(4)
352
339
353 # we don't want scintilla's autocompletion to choose
340 # we don't want scintilla's autocompletion to choose
354 # automaticaly out of a single choice list, as we pop it up
341 # automaticaly out of a single choice list, as we pop it up
355 # automaticaly
342 # automaticaly
356 self.AutoCompSetChooseSingle(False)
343 self.AutoCompSetChooseSingle(False)
357 self.AutoCompSetMaxHeight(10)
344 self.AutoCompSetMaxHeight(10)
358 # XXX: this doesn't seem to have an effect.
345 # XXX: this doesn't seem to have an effect.
359 self.AutoCompSetFillUps('\n')
346 self.AutoCompSetFillUps('\n')
360
347
361 self.SetMargins(3, 3) #text is moved away from border with 3px
348 self.SetMargins(3, 3) #text is moved away from border with 3px
362 # Suppressing Scintilla margins
349 # Suppressing Scintilla margins
363 self.SetMarginWidth(0, 0)
350 self.SetMarginWidth(0, 0)
364 self.SetMarginWidth(1, 0)
351 self.SetMarginWidth(1, 0)
365 self.SetMarginWidth(2, 0)
352 self.SetMarginWidth(2, 0)
366
353
367 # Xterm escape sequences
354 # Xterm escape sequences
368 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
355 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
369 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
356 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
370
357
371 # styles
358 # styles
372
359
373 if 'carret_color' in p:
360 if 'carret_color' in p:
374 self.SetCaretForeground(p['carret_color'])
361 self.SetCaretForeground(p['carret_color'])
375 else:
362 else:
376 self.SetCaretForeground('BLACK')
363 self.SetCaretForeground('BLACK')
377
364
378 if 'background_color' in p:
365 if 'background_color' in p:
379 background_color = p['background_color']
366 background_color = p['background_color']
380 else:
367 else:
381 background_color = 'WHITE'
368 background_color = 'WHITE'
382
369
383 if 'default' in p:
370 if 'default' in p:
384 if 'back' not in p['default']:
371 if 'back' not in p['default']:
385 p['default'] += ',back:%s' % background_color
372 p['default'] += ',back:%s' % background_color
386 if 'size' not in p['default']:
373 if 'size' not in p['default']:
387 p['default'] += ',size:%s' % self.faces['size']
374 p['default'] += ',size:%s' % self.faces['size']
388 if 'face' not in p['default']:
375 if 'face' not in p['default']:
389 p['default'] += ',face:%s' % self.faces['mono']
376 p['default'] += ',face:%s' % self.faces['mono']
390
377
391 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
378 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
392 else:
379 else:
393 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
380 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
394 "fore:%s,back:%s,size:%d,face:%s"
381 "fore:%s,back:%s,size:%d,face:%s"
395 % (self.ANSI_STYLES['0;30'][1],
382 % (self.ANSI_STYLES['0;30'][1],
396 background_color,
383 background_color,
397 self.faces['size'], self.faces['mono']))
384 self.faces['size'], self.faces['mono']))
398
385
399 #all styles = default one
386 #all styles = default one
400 self.StyleClearAll()
387 self.StyleClearAll()
401
388
402 # XXX: two lines below are usefull if not using the lexer
389 # XXX: two lines below are usefull if not using the lexer
403 #for style in self.ANSI_STYLES.values():
390 #for style in self.ANSI_STYLES.values():
404 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
391 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
405
392
406 #prompt definition
393 #prompt definition
407 if 'prompt_in1' in p:
394 if 'prompt_in1' in p:
408 self.prompt_in1 = p['prompt_in1']
395 self.prompt_in1 = p['prompt_in1']
409 else:
396 else:
410 self.prompt_in1 = \
397 self.prompt_in1 = \
411 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
398 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
412
399
413 if 'prompt_out' in p:
400 if 'prompt_out' in p:
414 self.prompt_out = p['prompt_out']
401 self.prompt_out = p['prompt_out']
415 else:
402 else:
416 self.prompt_out = \
403 self.prompt_out = \
417 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
404 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
418
405
419 self.output_prompt_template = string.Template(self.prompt_out)
406 self.output_prompt_template = string.Template(self.prompt_out)
420 self.input_prompt_template = string.Template(self.prompt_in1)
407 self.input_prompt_template = string.Template(self.prompt_in1)
421
408
422 if 'stdout' in p:
409 if 'stdout' in p:
423 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
410 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
424 if 'stderr' in p:
411 if 'stderr' in p:
425 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
412 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
426 if 'trace' in p:
413 if 'trace' in p:
427 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
414 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
428 if 'bracegood' in p:
415 if 'bracegood' in p:
429 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
416 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
430 if 'bracebad' in p:
417 if 'bracebad' in p:
431 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
418 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
432 if 'comment' in p:
419 if 'comment' in p:
433 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
420 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
434 if 'number' in p:
421 if 'number' in p:
435 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
422 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
436 if 'string' in p:
423 if 'string' in p:
437 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
424 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
438 if 'char' in p:
425 if 'char' in p:
439 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
426 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
440 if 'keyword' in p:
427 if 'keyword' in p:
441 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
428 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
442 if 'keyword' in p:
429 if 'keyword' in p:
443 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
430 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
444 if 'triple' in p:
431 if 'triple' in p:
445 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
432 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
446 if 'tripledouble' in p:
433 if 'tripledouble' in p:
447 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
434 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
448 if 'class' in p:
435 if 'class' in p:
449 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
436 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
450 if 'def' in p:
437 if 'def' in p:
451 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
438 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
452 if 'operator' in p:
439 if 'operator' in p:
453 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
440 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
454 if 'comment' in p:
441 if 'comment' in p:
455 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
442 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
456
443
457 if 'edge_column' in p:
444 if 'edge_column' in p:
458 edge_column = p['edge_column']
445 edge_column = p['edge_column']
459 if edge_column is not None and edge_column > 0:
446 if edge_column is not None and edge_column > 0:
460 #we add a vertical line to console widget
447 #we add a vertical line to console widget
461 self.SetEdgeMode(stc.STC_EDGE_LINE)
448 self.SetEdgeMode(stc.STC_EDGE_LINE)
462 self.SetEdgeColumn(88)
449 self.SetEdgeColumn(88)
463
450
464
451
452 #--------------------------------------------------------------------------
453 # EditWindow API
454 #--------------------------------------------------------------------------
455
456 def OnUpdateUI(self, event):
457 """ Override the OnUpdateUI of the EditWindow class, to prevent
458 syntax highlighting both for faster redraw, and for more
459 consistent look and feel.
460 """
461
462
465 #--------------------------------------------------------------------------
463 #--------------------------------------------------------------------------
466 # Private API
464 # Private API
467 #--------------------------------------------------------------------------
465 #--------------------------------------------------------------------------
468
466
469 def _on_key_down(self, event, skip=True):
467 def _on_key_down(self, event, skip=True):
470 """ Key press callback used for correcting behavior for
468 """ Key press callback used for correcting behavior for
471 console-like interfaces: the cursor is constraint to be after
469 console-like interfaces: the cursor is constraint to be after
472 the last prompt.
470 the last prompt.
473
471
474 Return True if event as been catched.
472 Return True if event as been catched.
475 """
473 """
476 catched = True
474 catched = True
477 # Intercept some specific keys.
475 # Intercept some specific keys.
478 if event.KeyCode == ord('L') and event.ControlDown() :
476 if event.KeyCode == ord('L') and event.ControlDown() :
479 self.scroll_to_bottom()
477 self.scroll_to_bottom()
480 elif event.KeyCode == ord('K') and event.ControlDown() :
478 elif event.KeyCode == ord('K') and event.ControlDown() :
481 self.input_buffer = ''
479 self.input_buffer = ''
482 elif event.KeyCode == ord('A') and event.ControlDown() :
480 elif event.KeyCode == ord('A') and event.ControlDown() :
483 self.GotoPos(self.GetLength())
481 self.GotoPos(self.GetLength())
484 self.SetSelectionStart(self.current_prompt_pos)
482 self.SetSelectionStart(self.current_prompt_pos)
485 self.SetSelectionEnd(self.GetCurrentPos())
483 self.SetSelectionEnd(self.GetCurrentPos())
486 catched = True
484 catched = True
487 elif event.KeyCode == ord('E') and event.ControlDown() :
485 elif event.KeyCode == ord('E') and event.ControlDown() :
488 self.GotoPos(self.GetLength())
486 self.GotoPos(self.GetLength())
489 catched = True
487 catched = True
490 elif event.KeyCode == wx.WXK_PAGEUP:
488 elif event.KeyCode == wx.WXK_PAGEUP:
491 self.ScrollPages(-1)
489 self.ScrollPages(-1)
492 elif event.KeyCode == wx.WXK_PAGEDOWN:
490 elif event.KeyCode == wx.WXK_PAGEDOWN:
493 self.ScrollPages(1)
491 self.ScrollPages(1)
494 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
492 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
495 self.ScrollLines(-1)
493 self.ScrollLines(-1)
496 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
494 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
497 self.ScrollLines(1)
495 self.ScrollLines(1)
498 else:
496 else:
499 catched = False
497 catched = False
500
498
501 if self.AutoCompActive():
499 if self.AutoCompActive():
502 event.Skip()
500 event.Skip()
503 else:
501 else:
504 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
502 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
505 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
503 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
506 catched = True
504 catched = True
507 if not self.enter_catched:
505 if not self.enter_catched:
508 self.CallTipCancel()
506 self.CallTipCancel()
509 self.write('\n', refresh=False)
507 self.write('\n', refresh=False)
510 # Under windows scintilla seems to be doing funny
508 # Under windows scintilla seems to be doing funny
511 # stuff to the line returns here, but the getter for
509 # stuff to the line returns here, but the getter for
512 # input_buffer filters this out.
510 # input_buffer filters this out.
513 if sys.platform == 'win32':
511 if sys.platform == 'win32':
514 self.input_buffer = self.input_buffer
512 self.input_buffer = self.input_buffer
515 self._on_enter()
513 self._on_enter()
516 self.enter_catched = True
514 self.enter_catched = True
517
515
518 elif event.KeyCode == wx.WXK_HOME:
516 elif event.KeyCode == wx.WXK_HOME:
519 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
517 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
520 self.GotoPos(self.current_prompt_pos)
518 self.GotoPos(self.current_prompt_pos)
521 catched = True
519 catched = True
522
520
523 elif event.Modifiers == wx.MOD_SHIFT:
521 elif event.Modifiers == wx.MOD_SHIFT:
524 # FIXME: This behavior is not ideal: if the selection
522 # FIXME: This behavior is not ideal: if the selection
525 # is already started, it will jump.
523 # is already started, it will jump.
526 self.SetSelectionStart(self.current_prompt_pos)
524 self.SetSelectionStart(self.current_prompt_pos)
527 self.SetSelectionEnd(self.GetCurrentPos())
525 self.SetSelectionEnd(self.GetCurrentPos())
528 catched = True
526 catched = True
529
527
530 elif event.KeyCode == wx.WXK_UP:
528 elif event.KeyCode == wx.WXK_UP:
531 if self.GetCurrentLine() > self.current_prompt_line:
529 if self.GetCurrentLine() > self.current_prompt_line:
532 if self.GetCurrentLine() == self.current_prompt_line + 1 \
530 if self.GetCurrentLine() == self.current_prompt_line + 1 \
533 and self.GetColumn(self.GetCurrentPos()) < \
531 and self.GetColumn(self.GetCurrentPos()) < \
534 self.GetColumn(self.current_prompt_pos):
532 self.GetColumn(self.current_prompt_pos):
535 self.GotoPos(self.current_prompt_pos)
533 self.GotoPos(self.current_prompt_pos)
536 else:
534 else:
537 event.Skip()
535 event.Skip()
538 catched = True
536 catched = True
539
537
540 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
538 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
541 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
539 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
542 event.Skip()
540 event.Skip()
543 catched = True
541 catched = True
544
542
545 elif event.KeyCode == wx.WXK_RIGHT:
543 elif event.KeyCode == wx.WXK_RIGHT:
546 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
544 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
547 event.Skip()
545 event.Skip()
548 catched = True
546 catched = True
549
547
550
548
551 elif event.KeyCode == wx.WXK_DELETE:
549 elif event.KeyCode == wx.WXK_DELETE:
552 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
550 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
553 event.Skip()
551 event.Skip()
554 catched = True
552 catched = True
555
553
556 if skip and not catched:
554 if skip and not catched:
557 # Put the cursor back in the edit region
555 # Put the cursor back in the edit region
558 if not self._keep_cursor_in_buffer():
556 if not self._keep_cursor_in_buffer():
559 if not (self.GetCurrentPos() == self.GetLength()
557 if not (self.GetCurrentPos() == self.GetLength()
560 and event.KeyCode == wx.WXK_DELETE):
558 and event.KeyCode == wx.WXK_DELETE):
561 event.Skip()
559 event.Skip()
562 catched = True
560 catched = True
563
561
564 return catched
562 return catched
565
563
566
564
567 def _on_key_up(self, event, skip=True):
565 def _on_key_up(self, event, skip=True):
568 """ If cursor is outside the editing region, put it back.
566 """ If cursor is outside the editing region, put it back.
569 """
567 """
570 if skip:
568 if skip:
571 event.Skip()
569 event.Skip()
572 self._keep_cursor_in_buffer()
570 self._keep_cursor_in_buffer()
573
571
574
572
573 # XXX: I need to avoid the problem of having an empty glass;
575 def _keep_cursor_in_buffer(self, pos=None):
574 def _keep_cursor_in_buffer(self, pos=None):
576 """ Checks if the cursor is where it is allowed to be. If not,
575 """ Checks if the cursor is where it is allowed to be. If not,
577 put it back.
576 put it back.
578
577
579 Returns
578 Returns
580 -------
579 -------
581 cursor_moved: Boolean
580 cursor_moved: Boolean
582 whether or not the cursor was moved by this routine.
581 whether or not the cursor was moved by this routine.
583
582
584 Notes
583 Notes
585 ------
584 ------
586 WARNING: This does proper checks only for horizontal
585 WARNING: This does proper checks only for horizontal
587 movements.
586 movements.
588 """
587 """
589 if pos is None:
588 if pos is None:
590 current_pos = self.GetCurrentPos()
589 current_pos = self.GetCurrentPos()
591 else:
590 else:
592 current_pos = pos
591 current_pos = pos
593 if current_pos < self.current_prompt_pos:
592 if current_pos < self.current_prompt_pos:
594 self.GotoPos(self.current_prompt_pos)
593 self.GotoPos(self.current_prompt_pos)
595 return True
594 return True
596 line_num = self.LineFromPosition(current_pos)
595 line_num = self.LineFromPosition(current_pos)
597 if not current_pos > self.GetLength():
596 if not current_pos > self.GetLength():
598 line_pos = self.GetColumn(current_pos)
597 line_pos = self.GetColumn(current_pos)
599 else:
598 else:
600 line_pos = self.GetColumn(self.GetLength())
599 line_pos = self.GetColumn(self.GetLength())
601 line = self.GetLine(line_num)
600 line = self.GetLine(line_num)
602 # Jump the continuation prompt
601 # Jump the continuation prompt
603 continuation_prompt = self.continuation_prompt()
602 continuation_prompt = self.continuation_prompt()
604 if ( line.startswith(continuation_prompt)
603 if ( line.startswith(continuation_prompt)
605 and line_pos < len(continuation_prompt)+1):
604 and line_pos < len(continuation_prompt)):
606 if line_pos < 2:
605 if line_pos < 2:
607 # We are at the beginning of the line, trying to move
606 # We are at the beginning of the line, trying to move
608 # forward: jump forward.
607 # forward: jump forward.
609 self.GotoPos(current_pos + 1 +
608 self.GotoPos(current_pos + 1 +
610 len(continuation_prompt) - line_pos)
609 len(continuation_prompt) - line_pos)
611 else:
610 else:
612 # Jump back up
611 # Jump back up
613 self.GotoPos(self.GetLineEndPosition(line_num-1))
612 self.GotoPos(self.GetLineEndPosition(line_num-1))
614 return True
613 return True
615 elif ( current_pos > self.GetLineEndPosition(line_num)
614 elif ( current_pos > self.GetLineEndPosition(line_num)
616 and not current_pos == self.GetLength()):
615 and not current_pos == self.GetLength()):
617 # Jump to next line
616 # Jump to next line
618 self.GotoPos(current_pos + 1 +
617 self.GotoPos(current_pos + 1 +
619 len(continuation_prompt))
618 len(continuation_prompt))
620 return True
619 return True
621
620
622 self.enter_catched = False #we re-allow enter event processing
621 self.enter_catched = False #we re-allow enter event processing
623 return False
622 return False
624
623
625
624
626 if __name__ == '__main__':
625 if __name__ == '__main__':
627 # Some simple code to test the console widget.
626 # Some simple code to test the console widget.
628 class MainWindow(wx.Frame):
627 class MainWindow(wx.Frame):
629 def __init__(self, parent, id, title):
628 def __init__(self, parent, id, title):
630 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
629 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
631 self._sizer = wx.BoxSizer(wx.VERTICAL)
630 self._sizer = wx.BoxSizer(wx.VERTICAL)
632 self.console_widget = ConsoleWidget(self)
631 self.console_widget = ConsoleWidget(self)
633 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
632 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
634 self.SetSizer(self._sizer)
633 self.SetSizer(self._sizer)
635 self.SetAutoLayout(1)
634 self.SetAutoLayout(1)
636 self.Show(True)
635 self.Show(True)
637
636
638 app = wx.PySimpleApp()
637 app = wx.PySimpleApp()
639 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
638 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
640 w.SetSize((780, 460))
639 w.SetSize((780, 460))
641 w.Show()
640 w.Show()
642
641
643 app.MainLoop()
642 app.MainLoop()
644
643
645
644
@@ -1,551 +1,583 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 if hidden:
287 if hidden:
288 return self.shell.execute(command)
288 return self.shell.execute(command)
289 else:
289 else:
290 # XXX: we are not storing the input buffer previous to the
290 # XXX: we are not storing the input buffer previous to the
291 # execution, as this forces us to run the execution
291 # execution, as this forces us to run the execution
292 # input_buffer a yield, which is not good.
292 # input_buffer a yield, which is not good.
293 ##current_buffer = self.shell.control.input_buffer
293 ##current_buffer = self.shell.control.input_buffer
294 command = command.rstrip()
294 command = command.rstrip()
295 if len(command.split('\n')) > 1:
295 if len(command.split('\n')) > 1:
296 # The input command is several lines long, we need to
296 # The input command is several lines long, we need to
297 # force the execution to happen
297 # force the execution to happen
298 command += '\n'
298 command += '\n'
299 cleaned_command = self.prefilter_input(command)
299 cleaned_command = self.prefilter_input(command)
300 self.input_buffer = command
300 self.input_buffer = command
301 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
301 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
302 # recursive yields.
302 # recursive yields.
303 self.ProcessEvent(wx.PaintEvent())
303 self.ProcessEvent(wx.PaintEvent())
304 self.write('\n')
304 self.write('\n')
305 if not self.is_complete(cleaned_command + '\n'):
305 if not self.is_complete(cleaned_command + '\n'):
306 self._colorize_input_buffer()
306 self._colorize_input_buffer()
307 self.render_error('Incomplete or invalid input')
307 self.render_error('Incomplete or invalid input')
308 self.new_prompt(self.input_prompt_template.substitute(
308 self.new_prompt(self.input_prompt_template.substitute(
309 number=(self.last_result['number'] + 1)))
309 number=(self.last_result['number'] + 1)))
310 return False
310 return False
311 self._on_enter()
311 self._on_enter()
312 return True
312 return True
313
313
314
314
315 def save_output_hooks(self):
315 def save_output_hooks(self):
316 self.__old_raw_input = __builtin__.raw_input
316 self.__old_raw_input = __builtin__.raw_input
317 PrefilterFrontEnd.save_output_hooks(self)
317 PrefilterFrontEnd.save_output_hooks(self)
318
318
319 def capture_output(self):
319 def capture_output(self):
320 self.SetLexer(stc.STC_LEX_NULL)
320 self.SetLexer(stc.STC_LEX_NULL)
321 PrefilterFrontEnd.capture_output(self)
321 PrefilterFrontEnd.capture_output(self)
322 __builtin__.raw_input = self.raw_input
322 __builtin__.raw_input = self.raw_input
323
323
324
324
325 def release_output(self):
325 def release_output(self):
326 __builtin__.raw_input = self.__old_raw_input
326 __builtin__.raw_input = self.__old_raw_input
327 PrefilterFrontEnd.release_output(self)
327 PrefilterFrontEnd.release_output(self)
328 self.SetLexer(stc.STC_LEX_PYTHON)
328 self.SetLexer(stc.STC_LEX_PYTHON)
329
329
330
330
331 def after_execute(self):
331 def after_execute(self):
332 PrefilterFrontEnd.after_execute(self)
332 PrefilterFrontEnd.after_execute(self)
333 # Clear the wait cursor
333 # Clear the wait cursor
334 if hasattr(self, '_cursor'):
334 if hasattr(self, '_cursor'):
335 del self._cursor
335 del self._cursor
336 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
336 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
337
337
338
338
339 def show_traceback(self):
339 def show_traceback(self):
340 start_line = self.GetCurrentLine()
340 start_line = self.GetCurrentLine()
341 PrefilterFrontEnd.show_traceback(self)
341 PrefilterFrontEnd.show_traceback(self)
342 self.ProcessEvent(wx.PaintEvent())
342 self.ProcessEvent(wx.PaintEvent())
343 #wx.Yield()
343 #wx.Yield()
344 for i in range(start_line, self.GetCurrentLine()):
344 for i in range(start_line, self.GetCurrentLine()):
345 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
345 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
346
346
347
347
348 #--------------------------------------------------------------------------
348 #--------------------------------------------------------------------------
349 # FrontEndBase interface
349 # FrontEndBase interface
350 #--------------------------------------------------------------------------
350 #--------------------------------------------------------------------------
351
351
352 def render_error(self, e):
352 def render_error(self, e):
353 start_line = self.GetCurrentLine()
353 start_line = self.GetCurrentLine()
354 self.write('\n' + e + '\n')
354 self.write('\n' + e + '\n')
355 for i in range(start_line, self.GetCurrentLine()):
355 for i in range(start_line, self.GetCurrentLine()):
356 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
356 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
357
357
358
358
359 #--------------------------------------------------------------------------
359 #--------------------------------------------------------------------------
360 # ConsoleWidget interface
360 # ConsoleWidget interface
361 #--------------------------------------------------------------------------
361 #--------------------------------------------------------------------------
362
362
363 def new_prompt(self, prompt):
363 def new_prompt(self, prompt):
364 """ Display a new prompt, and start a new input buffer.
364 """ Display a new prompt, and start a new input buffer.
365 """
365 """
366 self._input_state = 'readline'
366 self._input_state = 'readline'
367 ConsoleWidget.new_prompt(self, prompt)
367 ConsoleWidget.new_prompt(self, prompt)
368 i = self.current_prompt_line
368 i = self.current_prompt_line
369 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
369 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
370
370
371
371
372 def continuation_prompt(self, *args, **kwargs):
372 def continuation_prompt(self, *args, **kwargs):
373 # Avoid multiple inheritence, be explicit about which
373 # Avoid multiple inheritence, be explicit about which
374 # parent method class gets called
374 # parent method class gets called
375 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
375 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
376
376
377
377
378 def write(self, *args, **kwargs):
378 def write(self, *args, **kwargs):
379 # Avoid multiple inheritence, be explicit about which
379 # Avoid multiple inheritence, be explicit about which
380 # parent method class gets called
380 # parent method class gets called
381 return ConsoleWidget.write(self, *args, **kwargs)
381 return ConsoleWidget.write(self, *args, **kwargs)
382
382
383
383
384 def _on_key_down(self, event, skip=True):
384 def _on_key_down(self, event, skip=True):
385 """ Capture the character events, let the parent
385 """ Capture the character events, let the parent
386 widget handle them, and put our logic afterward.
386 widget handle them, and put our logic afterward.
387 """
387 """
388 # FIXME: This method needs to be broken down in smaller ones.
388 # FIXME: This method needs to be broken down in smaller ones.
389 current_line_number = self.GetCurrentLine()
389 current_line_number = self.GetCurrentLine()
390 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
390 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
391 # Capture Control-C
391 # Capture Control-C
392 if self._input_state == 'subprocess':
392 if self._input_state == 'subprocess':
393 if self.debug:
393 if self.debug:
394 print >>sys.__stderr__, 'Killing running process'
394 print >>sys.__stderr__, 'Killing running process'
395 if hasattr(self._running_process, 'process'):
395 if hasattr(self._running_process, 'process'):
396 self._running_process.process.kill()
396 self._running_process.process.kill()
397 elif self._input_state == 'buffering':
397 elif self._input_state == 'buffering':
398 if self.debug:
398 if self.debug:
399 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
399 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
400 raise KeyboardInterrupt
400 raise KeyboardInterrupt
401 # XXX: We need to make really sure we
401 # XXX: We need to make really sure we
402 # get back to a prompt.
402 # get back to a prompt.
403 elif self._input_state == 'subprocess' and (
403 elif self._input_state == 'subprocess' and (
404 ( event.KeyCode<256 and
404 ( event.KeyCode<256 and
405 not event.ControlDown() )
405 not event.ControlDown() )
406 or
406 or
407 ( event.KeyCode in (ord('d'), ord('D')) and
407 ( event.KeyCode in (ord('d'), ord('D')) and
408 event.ControlDown())):
408 event.ControlDown())):
409 # We are running a process, we redirect keys.
409 # We are running a process, we redirect keys.
410 ConsoleWidget._on_key_down(self, event, skip=skip)
410 ConsoleWidget._on_key_down(self, event, skip=skip)
411 char = chr(event.KeyCode)
411 char = chr(event.KeyCode)
412 # Deal with some inconsistency in wx keycodes:
412 # Deal with some inconsistency in wx keycodes:
413 if char == '\r':
413 if char == '\r':
414 char = '\n'
414 char = '\n'
415 elif not event.ShiftDown():
415 elif not event.ShiftDown():
416 char = char.lower()
416 char = char.lower()
417 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
417 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
418 char = '\04'
418 char = '\04'
419 self._running_process.process.stdin.write(char)
419 self._running_process.process.stdin.write(char)
420 self._running_process.process.stdin.flush()
420 self._running_process.process.stdin.flush()
421 elif event.KeyCode in (ord('('), 57, 53):
421 elif event.KeyCode in (ord('('), 57, 53):
422 # Calltips
422 # Calltips
423 event.Skip()
423 event.Skip()
424 self.do_calltip()
424 self.do_calltip()
425 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
425 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
426 event.Skip()
426 event.Skip()
427 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
427 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
428 wx.CallAfter(self._popup_completion, create=True)
428 wx.CallAfter(self._popup_completion, create=True)
429 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
429 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
430 wx.WXK_RIGHT, wx.WXK_ESCAPE):
430 wx.WXK_RIGHT, wx.WXK_ESCAPE):
431 wx.CallAfter(self._popup_completion)
431 wx.CallAfter(self._popup_completion)
432 else:
432 else:
433 # Up history
433 # Up history
434 if event.KeyCode == wx.WXK_UP and (
434 if event.KeyCode == wx.WXK_UP and (
435 ( current_line_number == self.current_prompt_line and
435 ( current_line_number == self.current_prompt_line and
436 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
436 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
437 or event.ControlDown() ):
437 or event.ControlDown() ):
438 new_buffer = self.get_history_previous(
438 new_buffer = self.get_history_previous(
439 self.input_buffer)
439 self.input_buffer)
440 if new_buffer is not None:
440 if new_buffer is not None:
441 self.input_buffer = new_buffer
441 self.input_buffer = new_buffer
442 if self.GetCurrentLine() > self.current_prompt_line:
442 if self.GetCurrentLine() > self.current_prompt_line:
443 # Go to first line, for seemless history up.
443 # Go to first line, for seemless history up.
444 self.GotoPos(self.current_prompt_pos)
444 self.GotoPos(self.current_prompt_pos)
445 # Down history
445 # Down history
446 elif event.KeyCode == wx.WXK_DOWN and (
446 elif event.KeyCode == wx.WXK_DOWN and (
447 ( current_line_number == self.LineCount -1 and
447 ( current_line_number == self.LineCount -1 and
448 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
448 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
449 or event.ControlDown() ):
449 or event.ControlDown() ):
450 new_buffer = self.get_history_next()
450 new_buffer = self.get_history_next()
451 if new_buffer is not None:
451 if new_buffer is not None:
452 self.input_buffer = new_buffer
452 self.input_buffer = new_buffer
453 # Tab-completion
453 # Tab-completion
454 elif event.KeyCode == ord('\t'):
454 elif event.KeyCode == ord('\t'):
455 current_line, current_line_number = self.CurLine
455 current_line, current_line_number = self.CurLine
456 if not re.match(r'^\s*$', current_line):
456 if not re.match(r'^\s*$', current_line):
457 self.complete_current_input()
457 self.complete_current_input()
458 if self.AutoCompActive():
458 if self.AutoCompActive():
459 wx.CallAfter(self._popup_completion, create=True)
459 wx.CallAfter(self._popup_completion, create=True)
460 else:
460 else:
461 event.Skip()
461 event.Skip()
462 elif event.KeyCode == wx.WXK_BACK:
463 # If characters where erased, check if we have to
464 # remove a line.
465 # XXX: What about DEL?
466 current_line, _ = self.CurLine
467 current_pos = self.GetCurrentPos()
468 current_line_number = self.LineFromPosition(current_pos)
469 current_col = self.GetColumn(current_pos)
470 len_prompt = len(self.continuation_prompt())
471 if ( current_line.startswith(self.continuation_prompt())
472 and current_col == len_prompt):
473 print 'BACK', current_line, self.current_prompt_line, \
474 current_line_number
475 new_lines = []
476 for line_num, line in enumerate(
477 self.input_buffer.split('\n')):
478 if (line_num + self.current_prompt_line ==
479 current_line_number):
480 new_lines.append(line[len_prompt:])
481 else:
482 new_lines.append('\n'+line)
483 # The first character is '\n', due to the above
484 # code:
485 self.input_buffer = ''.join(new_lines)[1:]
486 self.GotoPos(current_pos - 1 - len_prompt)
487 else:
488 ConsoleWidget._on_key_down(self, event, skip=skip)
462 else:
489 else:
463 ConsoleWidget._on_key_down(self, event, skip=skip)
490 ConsoleWidget._on_key_down(self, event, skip=skip)
491
464
492
465
493
466 def _on_key_up(self, event, skip=True):
494 def _on_key_up(self, event, skip=True):
467 """ Called when any key is released.
495 """ Called when any key is released.
468 """
496 """
469 if event.KeyCode in (59, ord('.')):
497 if event.KeyCode in (59, ord('.')):
470 # Intercepting '.'
498 # Intercepting '.'
471 event.Skip()
499 event.Skip()
472 wx.CallAfter(self._popup_completion, create=True)
500 wx.CallAfter(self._popup_completion, create=True)
473 else:
501 else:
474 ConsoleWidget._on_key_up(self, event, skip=skip)
502 ConsoleWidget._on_key_up(self, event, skip=skip)
475 if (self.input_buffer.split('\n')[-1] == self.continuation_prompt()
503 # Make sure the continuation_prompts are always followed by a
476 and self._input_state == 'readline'):
504 # whitespace
477 # Make sure the continuation_prompt is followed by a whitespace
505 new_lines = []
506 if self._input_state == 'readline':
478 position = self.GetCurrentPos()
507 position = self.GetCurrentPos()
479 self.input_buffer += ' '
508 for line in self.input_buffer.split('\n'):
509 if not line == self.continuation_prompt()[:-1]:
510 new_lines.append(line)
511 self.input_buffer = '\n'.join(new_lines)
480 self.GotoPos(position)
512 self.GotoPos(position)
481
513
482
514
483 def _on_enter(self):
515 def _on_enter(self):
484 """ Called on return key down, in readline input_state.
516 """ Called on return key down, in readline input_state.
485 """
517 """
486 if self.debug:
518 if self.debug:
487 print >>sys.__stdout__, repr(self.input_buffer)
519 print >>sys.__stdout__, repr(self.input_buffer)
488 PrefilterFrontEnd._on_enter(self)
520 PrefilterFrontEnd._on_enter(self)
489
521
490
522
491 #--------------------------------------------------------------------------
523 #--------------------------------------------------------------------------
492 # EditWindow API
524 # EditWindow API
493 #--------------------------------------------------------------------------
525 #--------------------------------------------------------------------------
494
526
495 def OnUpdateUI(self, event):
527 def OnUpdateUI(self, event):
496 """ Override the OnUpdateUI of the EditWindow class, to prevent
528 """ Override the OnUpdateUI of the EditWindow class, to prevent
497 syntax highlighting both for faster redraw, and for more
529 syntax highlighting both for faster redraw, and for more
498 consistent look and feel.
530 consistent look and feel.
499 """
531 """
500 if not self._input_state == 'readline':
532 if not self._input_state == 'readline':
501 ConsoleWidget.OnUpdateUI(self, event)
533 ConsoleWidget.OnUpdateUI(self, event)
502
534
503 #--------------------------------------------------------------------------
535 #--------------------------------------------------------------------------
504 # Private API
536 # Private API
505 #--------------------------------------------------------------------------
537 #--------------------------------------------------------------------------
506
538
507 def _buffer_flush(self, event):
539 def _buffer_flush(self, event):
508 """ Called by the timer to flush the write buffer.
540 """ Called by the timer to flush the write buffer.
509
541
510 This is always called in the mainloop, by the wx timer.
542 This is always called in the mainloop, by the wx timer.
511 """
543 """
512 self._out_buffer_lock.acquire()
544 self._out_buffer_lock.acquire()
513 _out_buffer = self._out_buffer
545 _out_buffer = self._out_buffer
514 self._out_buffer = []
546 self._out_buffer = []
515 self._out_buffer_lock.release()
547 self._out_buffer_lock.release()
516 self.write(''.join(_out_buffer), refresh=False)
548 self.write(''.join(_out_buffer), refresh=False)
517
549
518
550
519 def _colorize_input_buffer(self):
551 def _colorize_input_buffer(self):
520 """ Keep the input buffer lines at a bright color.
552 """ Keep the input buffer lines at a bright color.
521 """
553 """
522 if not self._input_state in ('readline', 'raw_input'):
554 if not self._input_state in ('readline', 'raw_input'):
523 return
555 return
524 end_line = self.GetCurrentLine()
556 end_line = self.GetCurrentLine()
525 if not sys.platform == 'win32':
557 if not sys.platform == 'win32':
526 end_line += 1
558 end_line += 1
527 for i in range(self.current_prompt_line, end_line):
559 for i in range(self.current_prompt_line, end_line):
528 if i in self._markers:
560 if i in self._markers:
529 self.MarkerDeleteHandle(self._markers[i])
561 self.MarkerDeleteHandle(self._markers[i])
530 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
562 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
531
563
532
564
533 if __name__ == '__main__':
565 if __name__ == '__main__':
534 class MainWindow(wx.Frame):
566 class MainWindow(wx.Frame):
535 def __init__(self, parent, id, title):
567 def __init__(self, parent, id, title):
536 wx.Frame.__init__(self, parent, id, title, size=(300,250))
568 wx.Frame.__init__(self, parent, id, title, size=(300,250))
537 self._sizer = wx.BoxSizer(wx.VERTICAL)
569 self._sizer = wx.BoxSizer(wx.VERTICAL)
538 self.shell = WxController(self)
570 self.shell = WxController(self)
539 self._sizer.Add(self.shell, 1, wx.EXPAND)
571 self._sizer.Add(self.shell, 1, wx.EXPAND)
540 self.SetSizer(self._sizer)
572 self.SetSizer(self._sizer)
541 self.SetAutoLayout(1)
573 self.SetAutoLayout(1)
542 self.Show(True)
574 self.Show(True)
543
575
544 app = wx.PySimpleApp()
576 app = wx.PySimpleApp()
545 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
577 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
546 frame.shell.SetFocus()
578 frame.shell.SetFocus()
547 frame.SetSize((680, 460))
579 frame.SetSize((680, 460))
548 self = frame.shell
580 self = frame.shell
549
581
550 app.MainLoop()
582 app.MainLoop()
551
583
General Comments 0
You need to be logged in to leave comments. Login now