##// END OF EJS Templates
Minor formatting.
Gael Varoquaux -
Show More
@@ -1,655 +1,656 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
246 We need to implement this method here to deal with the
247 ascii escape sequences cleaning up.
247 ascii escape sequences cleaning up.
248 """
248 """
249 # ASCII-less prompt
249 # ASCII-less prompt
250 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])
251 return "."*(len(ascii_less)-2) + ': '
251 return "."*(len(ascii_less)-2) + ': '
252
252
253
253
254 def scroll_to_bottom(self):
254 def scroll_to_bottom(self):
255 maxrange = self.GetScrollRange(wx.VERTICAL)
255 maxrange = self.GetScrollRange(wx.VERTICAL)
256 self.ScrollLines(maxrange)
256 self.ScrollLines(maxrange)
257
257
258
258
259 def pop_completion(self, possibilities, offset=0):
259 def pop_completion(self, possibilities, offset=0):
260 """ Pops up an autocompletion menu. Offset is the offset
260 """ Pops up an autocompletion menu. Offset is the offset
261 in characters of the position at which the menu should
261 in characters of the position at which the menu should
262 appear, relativ to the cursor.
262 appear, relativ to the cursor.
263 """
263 """
264 self.AutoCompSetIgnoreCase(False)
264 self.AutoCompSetIgnoreCase(False)
265 self.AutoCompSetAutoHide(False)
265 self.AutoCompSetAutoHide(False)
266 self.AutoCompSetMaxHeight(len(possibilities))
266 self.AutoCompSetMaxHeight(len(possibilities))
267 self.AutoCompShow(offset, " ".join(possibilities))
267 self.AutoCompShow(offset, " ".join(possibilities))
268
268
269
269
270 def get_line_width(self):
270 def get_line_width(self):
271 """ Return the width of the line in characters.
271 """ Return the width of the line in characters.
272 """
272 """
273 return self.GetSize()[0]/self.GetCharWidth()
273 return self.GetSize()[0]/self.GetCharWidth()
274
274
275
275
276 def configure_scintilla(self):
276 def configure_scintilla(self):
277
277
278 p = self.style
278 p = self.style
279
279
280 #First we define the special background colors
280 #First we define the special background colors
281 if 'trace' in p:
281 if 'trace' in p:
282 _COMPLETE_BUFFER_BG = p['trace']
282 _COMPLETE_BUFFER_BG = p['trace']
283 else:
283 else:
284 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
284 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
285
285
286 if 'stdout' in p:
286 if 'stdout' in p:
287 _INPUT_BUFFER_BG = p['stdout']
287 _INPUT_BUFFER_BG = p['stdout']
288 else:
288 else:
289 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
289 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
290
290
291 if 'stderr' in p:
291 if 'stderr' in p:
292 _ERROR_BG = p['stderr']
292 _ERROR_BG = p['stderr']
293 else:
293 else:
294 _ERROR_BG = '#FFF1F1' # Nice red
294 _ERROR_BG = '#FFF1F1' # Nice red
295
295
296 # Marker for complete buffer.
296 # Marker for complete buffer.
297 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
297 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
298 background = _COMPLETE_BUFFER_BG)
298 background = _COMPLETE_BUFFER_BG)
299
299
300 # Marker for current input buffer.
300 # Marker for current input buffer.
301 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
301 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
302 background = _INPUT_BUFFER_BG)
302 background = _INPUT_BUFFER_BG)
303 # Marker for tracebacks.
303 # Marker for tracebacks.
304 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
304 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
305 background = _ERROR_BG)
305 background = _ERROR_BG)
306
306
307 self.SetEOLMode(stc.STC_EOL_LF)
307 self.SetEOLMode(stc.STC_EOL_LF)
308
308
309 # 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
310 # the widget
310 # the widget
311 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
311 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
312 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
312 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
313 # Also allow Ctrl Shift "=" for poor non US keyboard users.
313 # Also allow Ctrl Shift "=" for poor non US keyboard users.
314 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
314 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
315 stc.STC_CMD_ZOOMIN)
315 stc.STC_CMD_ZOOMIN)
316
316
317 # 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
318 # well with a console.
318 # well with a console.
319 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
319 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
320 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
320 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
321 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
321 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
322 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
322 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
323
323
324 self.SetEOLMode(stc.STC_EOL_CRLF)
324 self.SetEOLMode(stc.STC_EOL_CRLF)
325 self.SetWrapMode(stc.STC_WRAP_CHAR)
325 self.SetWrapMode(stc.STC_WRAP_CHAR)
326 self.SetWrapMode(stc.STC_WRAP_WORD)
326 self.SetWrapMode(stc.STC_WRAP_WORD)
327 self.SetBufferedDraw(True)
327 self.SetBufferedDraw(True)
328
328
329 if 'antialiasing' in p:
329 if 'antialiasing' in p:
330 self.SetUseAntiAliasing(p['antialiasing'])
330 self.SetUseAntiAliasing(p['antialiasing'])
331 else:
331 else:
332 self.SetUseAntiAliasing(True)
332 self.SetUseAntiAliasing(True)
333
333
334 self.SetLayoutCache(stc.STC_CACHE_PAGE)
334 self.SetLayoutCache(stc.STC_CACHE_PAGE)
335 self.SetUndoCollection(False)
335 self.SetUndoCollection(False)
336 self.SetUseTabs(True)
336 self.SetUseTabs(True)
337 self.SetIndent(4)
337 self.SetIndent(4)
338 self.SetTabWidth(4)
338 self.SetTabWidth(4)
339
339
340 # we don't want scintilla's autocompletion to choose
340 # we don't want scintilla's autocompletion to choose
341 # 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
342 # automaticaly
342 # automaticaly
343 self.AutoCompSetChooseSingle(False)
343 self.AutoCompSetChooseSingle(False)
344 self.AutoCompSetMaxHeight(10)
344 self.AutoCompSetMaxHeight(10)
345 # XXX: this doesn't seem to have an effect.
345 # XXX: this doesn't seem to have an effect.
346 self.AutoCompSetFillUps('\n')
346 self.AutoCompSetFillUps('\n')
347
347
348 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
349 # Suppressing Scintilla margins
349 # Suppressing Scintilla margins
350 self.SetMarginWidth(0, 0)
350 self.SetMarginWidth(0, 0)
351 self.SetMarginWidth(1, 0)
351 self.SetMarginWidth(1, 0)
352 self.SetMarginWidth(2, 0)
352 self.SetMarginWidth(2, 0)
353
353
354 # Xterm escape sequences
354 # Xterm escape sequences
355 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
355 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
356 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
356 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
357
357
358 # styles
358 # styles
359
359
360 if 'carret_color' in p:
360 if 'carret_color' in p:
361 self.SetCaretForeground(p['carret_color'])
361 self.SetCaretForeground(p['carret_color'])
362 else:
362 else:
363 self.SetCaretForeground('BLACK')
363 self.SetCaretForeground('BLACK')
364
364
365 if 'background_color' in p:
365 if 'background_color' in p:
366 background_color = p['background_color']
366 background_color = p['background_color']
367 else:
367 else:
368 background_color = 'WHITE'
368 background_color = 'WHITE'
369
369
370 if 'default' in p:
370 if 'default' in p:
371 if 'back' not in p['default']:
371 if 'back' not in p['default']:
372 p['default'] += ',back:%s' % background_color
372 p['default'] += ',back:%s' % background_color
373 if 'size' not in p['default']:
373 if 'size' not in p['default']:
374 p['default'] += ',size:%s' % self.faces['size']
374 p['default'] += ',size:%s' % self.faces['size']
375 if 'face' not in p['default']:
375 if 'face' not in p['default']:
376 p['default'] += ',face:%s' % self.faces['mono']
376 p['default'] += ',face:%s' % self.faces['mono']
377
377
378 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
378 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
379 else:
379 else:
380 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
380 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
381 "fore:%s,back:%s,size:%d,face:%s"
381 "fore:%s,back:%s,size:%d,face:%s"
382 % (self.ANSI_STYLES['0;30'][1],
382 % (self.ANSI_STYLES['0;30'][1],
383 background_color,
383 background_color,
384 self.faces['size'], self.faces['mono']))
384 self.faces['size'], self.faces['mono']))
385
385
386 #all styles = default one
386 #all styles = default one
387 self.StyleClearAll()
387 self.StyleClearAll()
388
388
389 # XXX: two lines below are usefull if not using the lexer
389 # XXX: two lines below are usefull if not using the lexer
390 #for style in self.ANSI_STYLES.values():
390 #for style in self.ANSI_STYLES.values():
391 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
391 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
392
392
393 #prompt definition
393 #prompt definition
394 if 'prompt_in1' in p:
394 if 'prompt_in1' in p:
395 self.prompt_in1 = p['prompt_in1']
395 self.prompt_in1 = p['prompt_in1']
396 else:
396 else:
397 self.prompt_in1 = \
397 self.prompt_in1 = \
398 '\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'
399
399
400 if 'prompt_out' in p:
400 if 'prompt_out' in p:
401 self.prompt_out = p['prompt_out']
401 self.prompt_out = p['prompt_out']
402 else:
402 else:
403 self.prompt_out = \
403 self.prompt_out = \
404 '\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'
405
405
406 self.output_prompt_template = string.Template(self.prompt_out)
406 self.output_prompt_template = string.Template(self.prompt_out)
407 self.input_prompt_template = string.Template(self.prompt_in1)
407 self.input_prompt_template = string.Template(self.prompt_in1)
408
408
409 if 'stdout' in p:
409 if 'stdout' in p:
410 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
410 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
411 if 'stderr' in p:
411 if 'stderr' in p:
412 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
412 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
413 if 'trace' in p:
413 if 'trace' in p:
414 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
414 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
415 if 'bracegood' in p:
415 if 'bracegood' in p:
416 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
416 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
417 if 'bracebad' in p:
417 if 'bracebad' in p:
418 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
418 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
419 if 'comment' in p:
419 if 'comment' in p:
420 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
420 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
421 if 'number' in p:
421 if 'number' in p:
422 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
422 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
423 if 'string' in p:
423 if 'string' in p:
424 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
424 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
425 if 'char' in p:
425 if 'char' in p:
426 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
426 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
427 if 'keyword' in p:
427 if 'keyword' in p:
428 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
428 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
429 if 'keyword' in p:
429 if 'keyword' in p:
430 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
430 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
431 if 'triple' in p:
431 if 'triple' in p:
432 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
432 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
433 if 'tripledouble' in p:
433 if 'tripledouble' in p:
434 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
434 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
435 if 'class' in p:
435 if 'class' in p:
436 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
436 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
437 if 'def' in p:
437 if 'def' in p:
438 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
438 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
439 if 'operator' in p:
439 if 'operator' in p:
440 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
440 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
441 if 'comment' in p:
441 if 'comment' in p:
442 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
442 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
443
443
444 if 'edge_column' in p:
444 if 'edge_column' in p:
445 edge_column = p['edge_column']
445 edge_column = p['edge_column']
446 if edge_column is not None and edge_column > 0:
446 if edge_column is not None and edge_column > 0:
447 #we add a vertical line to console widget
447 #we add a vertical line to console widget
448 self.SetEdgeMode(stc.STC_EDGE_LINE)
448 self.SetEdgeMode(stc.STC_EDGE_LINE)
449 self.SetEdgeColumn(88)
449 self.SetEdgeColumn(88)
450
450
451
451
452 #--------------------------------------------------------------------------
452 #--------------------------------------------------------------------------
453 # EditWindow API
453 # EditWindow API
454 #--------------------------------------------------------------------------
454 #--------------------------------------------------------------------------
455
455
456 def OnUpdateUI(self, event):
456 def OnUpdateUI(self, event):
457 """ Override the OnUpdateUI of the EditWindow class, to prevent
457 """ Override the OnUpdateUI of the EditWindow class, to prevent
458 syntax highlighting both for faster redraw, and for more
458 syntax highlighting both for faster redraw, and for more
459 consistent look and feel.
459 consistent look and feel.
460 """
460 """
461
461
462
462
463 #--------------------------------------------------------------------------
463 #--------------------------------------------------------------------------
464 # Private API
464 # Private API
465 #--------------------------------------------------------------------------
465 #--------------------------------------------------------------------------
466
466
467 def _on_key_down(self, event, skip=True):
467 def _on_key_down(self, event, skip=True):
468 """ Key press callback used for correcting behavior for
468 """ Key press callback used for correcting behavior for
469 console-like interfaces: the cursor is constraint to be after
469 console-like interfaces: the cursor is constraint to be after
470 the last prompt.
470 the last prompt.
471
471
472 Return True if event as been catched.
472 Return True if event as been catched.
473 """
473 """
474 catched = True
474 catched = True
475 # XXX: Would the right way to do this be to have a
475 # XXX: Would the right way to do this be to have a
476 # dictionary at the instance level associating keys with
476 # dictionary at the instance level associating keys with
477 # callbacks? How would we deal with inheritance? And Do the
477 # callbacks? How would we deal with inheritance? And Do the
478 # different callbacks share local variables?
478 # different callbacks share local variables?
479
479
480 # Intercept some specific keys.
480 # Intercept some specific keys.
481 if event.KeyCode == ord('L') and event.ControlDown() :
481 if event.KeyCode == ord('L') and event.ControlDown() :
482 self.scroll_to_bottom()
482 self.scroll_to_bottom()
483 elif event.KeyCode == ord('K') and event.ControlDown() :
483 elif event.KeyCode == ord('K') and event.ControlDown() :
484 self.input_buffer = ''
484 self.input_buffer = ''
485 elif event.KeyCode == ord('A') and event.ControlDown() :
485 elif event.KeyCode == ord('A') and event.ControlDown() :
486 self.GotoPos(self.GetLength())
486 self.GotoPos(self.GetLength())
487 self.SetSelectionStart(self.current_prompt_pos)
487 self.SetSelectionStart(self.current_prompt_pos)
488 self.SetSelectionEnd(self.GetCurrentPos())
488 self.SetSelectionEnd(self.GetCurrentPos())
489 catched = True
489 catched = True
490 elif event.KeyCode == ord('E') and event.ControlDown() :
490 elif event.KeyCode == ord('E') and event.ControlDown() :
491 self.GotoPos(self.GetLength())
491 self.GotoPos(self.GetLength())
492 catched = True
492 catched = True
493 elif event.KeyCode == wx.WXK_PAGEUP:
493 elif event.KeyCode == wx.WXK_PAGEUP:
494 self.ScrollPages(-1)
494 self.ScrollPages(-1)
495 elif event.KeyCode == wx.WXK_PAGEDOWN:
495 elif event.KeyCode == wx.WXK_PAGEDOWN:
496 self.ScrollPages(1)
496 self.ScrollPages(1)
497 elif event.KeyCode == wx.WXK_HOME:
497 elif event.KeyCode == wx.WXK_HOME:
498 self.GotoPos(self.GetLength())
498 self.GotoPos(self.GetLength())
499 elif event.KeyCode == wx.WXK_END:
499 elif event.KeyCode == wx.WXK_END:
500 self.GotoPos(self.GetLength())
500 self.GotoPos(self.GetLength())
501 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
501 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
502 self.ScrollLines(-1)
502 self.ScrollLines(-1)
503 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
503 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
504 self.ScrollLines(1)
504 self.ScrollLines(1)
505 else:
505 else:
506 catched = False
506 catched = False
507
507
508 if self.AutoCompActive():
508 if self.AutoCompActive():
509 event.Skip()
509 event.Skip()
510 else:
510 else:
511 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
511 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
512 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
512 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
513 wx.MOD_SHIFT):
513 wx.MOD_SHIFT):
514 catched = True
514 catched = True
515 if not self.enter_catched:
515 if not self.enter_catched:
516 self.CallTipCancel()
516 self.CallTipCancel()
517 if event.Modifiers == wx.MOD_SHIFT:
517 if event.Modifiers == wx.MOD_SHIFT:
518 # Try to force execution
518 # Try to force execution
519 self.GotoPos(self.GetLength())
519 self.GotoPos(self.GetLength())
520 self.write('\n' + self.continuation_prompt(),
520 self.write('\n' + self.continuation_prompt(),
521 refresh=False)
521 refresh=False)
522 self._on_enter()
522 self._on_enter()
523 else:
523 else:
524 self._on_enter()
524 self._on_enter()
525 self.enter_catched = True
525 self.enter_catched = True
526
526
527 elif event.KeyCode == wx.WXK_HOME:
527 elif event.KeyCode == wx.WXK_HOME:
528 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
528 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
529 self.GotoPos(self.current_prompt_pos)
529 self.GotoPos(self.current_prompt_pos)
530 catched = True
530 catched = True
531
531
532 elif event.Modifiers == wx.MOD_SHIFT:
532 elif event.Modifiers == wx.MOD_SHIFT:
533 # FIXME: This behavior is not ideal: if the selection
533 # FIXME: This behavior is not ideal: if the selection
534 # is already started, it will jump.
534 # is already started, it will jump.
535 self.SetSelectionStart(self.current_prompt_pos)
535 self.SetSelectionStart(self.current_prompt_pos)
536 self.SetSelectionEnd(self.GetCurrentPos())
536 self.SetSelectionEnd(self.GetCurrentPos())
537 catched = True
537 catched = True
538
538
539 elif event.KeyCode == wx.WXK_UP:
539 elif event.KeyCode == wx.WXK_UP:
540 if self.GetCurrentLine() > self.current_prompt_line:
540 if self.GetCurrentLine() > self.current_prompt_line:
541 if self.GetCurrentLine() == self.current_prompt_line + 1 \
541 if self.GetCurrentLine() == self.current_prompt_line + 1 \
542 and self.GetColumn(self.GetCurrentPos()) < \
542 and self.GetColumn(self.GetCurrentPos()) < \
543 self.GetColumn(self.current_prompt_pos):
543 self.GetColumn(self.current_prompt_pos):
544 self.GotoPos(self.current_prompt_pos)
544 self.GotoPos(self.current_prompt_pos)
545 else:
545 else:
546 event.Skip()
546 event.Skip()
547 catched = True
547 catched = True
548
548
549 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
549 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
550 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
550 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
551 event.Skip()
551 event.Skip()
552 catched = True
552 catched = True
553
553
554 elif event.KeyCode == wx.WXK_RIGHT:
554 elif event.KeyCode == wx.WXK_RIGHT:
555 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
555 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
556 event.Skip()
556 event.Skip()
557 catched = True
557 catched = True
558
558
559
559
560 elif event.KeyCode == wx.WXK_DELETE:
560 elif event.KeyCode == wx.WXK_DELETE:
561 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
561 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
562 event.Skip()
562 event.Skip()
563 catched = True
563 catched = True
564
564
565 if skip and not catched:
565 if skip and not catched:
566 # Put the cursor back in the edit region
566 # Put the cursor back in the edit region
567 if not self._keep_cursor_in_buffer():
567 if not self._keep_cursor_in_buffer():
568 if not (self.GetCurrentPos() == self.GetLength()
568 if not (self.GetCurrentPos() == self.GetLength()
569 and event.KeyCode == wx.WXK_DELETE):
569 and event.KeyCode == wx.WXK_DELETE):
570 event.Skip()
570 event.Skip()
571 catched = True
571 catched = True
572
572
573 return catched
573 return catched
574
574
575
575
576 def _on_key_up(self, event, skip=True):
576 def _on_key_up(self, event, skip=True):
577 """ If cursor is outside the editing region, put it back.
577 """ If cursor is outside the editing region, put it back.
578 """
578 """
579 if skip:
579 if skip:
580 event.Skip()
580 event.Skip()
581 self._keep_cursor_in_buffer()
581 self._keep_cursor_in_buffer()
582
582
583
583
584 # XXX: I need to avoid the problem of having an empty glass;
584 # XXX: I need to avoid the problem of having an empty glass;
585 def _keep_cursor_in_buffer(self, pos=None):
585 def _keep_cursor_in_buffer(self, pos=None):
586 """ Checks if the cursor is where it is allowed to be. If not,
586 """ Checks if the cursor is where it is allowed to be. If not,
587 put it back.
587 put it back.
588
588
589 Returns
589 Returns
590 -------
590 -------
591 cursor_moved: Boolean
591 cursor_moved: Boolean
592 whether or not the cursor was moved by this routine.
592 whether or not the cursor was moved by this routine.
593
593
594 Notes
594 Notes
595 ------
595 ------
596 WARNING: This does proper checks only for horizontal
596 WARNING: This does proper checks only for horizontal
597 movements.
597 movements.
598 """
598 """
599 if pos is None:
599 if pos is None:
600 current_pos = self.GetCurrentPos()
600 current_pos = self.GetCurrentPos()
601 else:
601 else:
602 current_pos = pos
602 current_pos = pos
603 if current_pos < self.current_prompt_pos:
603 if current_pos < self.current_prompt_pos:
604 self.GotoPos(self.current_prompt_pos)
604 self.GotoPos(self.current_prompt_pos)
605 return True
605 return True
606 line_num = self.LineFromPosition(current_pos)
606 line_num = self.LineFromPosition(current_pos)
607 if not current_pos > self.GetLength():
607 if not current_pos > self.GetLength():
608 line_pos = self.GetColumn(current_pos)
608 line_pos = self.GetColumn(current_pos)
609 else:
609 else:
610 line_pos = self.GetColumn(self.GetLength())
610 line_pos = self.GetColumn(self.GetLength())
611 line = self.GetLine(line_num)
611 line = self.GetLine(line_num)
612 # Jump the continuation prompt
612 # Jump the continuation prompt
613 continuation_prompt = self.continuation_prompt()
613 continuation_prompt = self.continuation_prompt()
614 if ( line.startswith(continuation_prompt)
614 if ( line.startswith(continuation_prompt)
615 and line_pos < len(continuation_prompt)):
615 and line_pos < len(continuation_prompt)):
616 if line_pos < 2:
616 if line_pos < 2:
617 # We are at the beginning of the line, trying to move
617 # We are at the beginning of the line, trying to move
618 # forward: jump forward.
618 # forward: jump forward.
619 self.GotoPos(current_pos + 1 +
619 self.GotoPos(current_pos + 1 +
620 len(continuation_prompt) - line_pos)
620 len(continuation_prompt) - line_pos)
621 else:
621 else:
622 # Jump back up
622 # Jump back up
623 self.GotoPos(self.GetLineEndPosition(line_num-1))
623 self.GotoPos(self.GetLineEndPosition(line_num-1))
624 return True
624 return True
625 elif ( current_pos > self.GetLineEndPosition(line_num)
625 elif ( current_pos > self.GetLineEndPosition(line_num)
626 and not current_pos == self.GetLength()):
626 and not current_pos == self.GetLength()):
627 # Jump to next line
627 # Jump to next line
628 self.GotoPos(current_pos + 1 +
628 self.GotoPos(current_pos + 1 +
629 len(continuation_prompt))
629 len(continuation_prompt))
630 return True
630 return True
631
631
632 self.enter_catched = False #we re-allow enter event processing
632 # We re-allow enter event processing
633 self.enter_catched = False
633 return False
634 return False
634
635
635
636
636 if __name__ == '__main__':
637 if __name__ == '__main__':
637 # Some simple code to test the console widget.
638 # Some simple code to test the console widget.
638 class MainWindow(wx.Frame):
639 class MainWindow(wx.Frame):
639 def __init__(self, parent, id, title):
640 def __init__(self, parent, id, title):
640 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
641 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
641 self._sizer = wx.BoxSizer(wx.VERTICAL)
642 self._sizer = wx.BoxSizer(wx.VERTICAL)
642 self.console_widget = ConsoleWidget(self)
643 self.console_widget = ConsoleWidget(self)
643 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
644 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
644 self.SetSizer(self._sizer)
645 self.SetSizer(self._sizer)
645 self.SetAutoLayout(1)
646 self.SetAutoLayout(1)
646 self.Show(True)
647 self.Show(True)
647
648
648 app = wx.PySimpleApp()
649 app = wx.PySimpleApp()
649 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
650 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
650 w.SetSize((780, 460))
651 w.SetSize((780, 460))
651 w.Show()
652 w.Show()
652
653
653 app.MainLoop()
654 app.MainLoop()
654
655
655
656
General Comments 0
You need to be logged in to leave comments. Login now