##// END OF EJS Templates
Faster refresh on write. Avoid recursive Yields.
gvaroquaux -
Show More
@@ -1,435 +1,436 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 LINESEP = '\n'
28 LINESEP = '\n'
29 if sys.platform == 'win32':
29 if sys.platform == 'win32':
30 LINESEP = '\n\r'
30 LINESEP = '\n\r'
31
31
32 import re
32 import re
33
33
34 # FIXME: Need to provide an API for non user-generated display on the
34 # FIXME: Need to provide an API for non user-generated display on the
35 # screen: this should not be editable by the user.
35 # screen: this should not be editable by the user.
36
36
37 _DEFAULT_SIZE = 10
37 _DEFAULT_SIZE = 10
38 if sys.platform == 'darwin':
38 if sys.platform == 'darwin':
39 _DEFAULT_STYLE = 12
39 _DEFAULT_STYLE = 12
40
40
41 _DEFAULT_STYLE = {
41 _DEFAULT_STYLE = {
42 'stdout' : 'fore:#0000FF',
42 'stdout' : 'fore:#0000FF',
43 'stderr' : 'fore:#007f00',
43 'stderr' : 'fore:#007f00',
44 'trace' : 'fore:#FF0000',
44 'trace' : 'fore:#FF0000',
45
45
46 'default' : 'size:%d' % _DEFAULT_SIZE,
46 'default' : 'size:%d' % _DEFAULT_SIZE,
47 'bracegood' : 'fore:#00AA00,back:#000000,bold',
47 'bracegood' : 'fore:#00AA00,back:#000000,bold',
48 'bracebad' : 'fore:#FF0000,back:#000000,bold',
48 'bracebad' : 'fore:#FF0000,back:#000000,bold',
49
49
50 # properties for the various Python lexer styles
50 # properties for the various Python lexer styles
51 'comment' : 'fore:#007F00',
51 'comment' : 'fore:#007F00',
52 'number' : 'fore:#007F7F',
52 'number' : 'fore:#007F7F',
53 'string' : 'fore:#7F007F,italic',
53 'string' : 'fore:#7F007F,italic',
54 'char' : 'fore:#7F007F,italic',
54 'char' : 'fore:#7F007F,italic',
55 'keyword' : 'fore:#00007F,bold',
55 'keyword' : 'fore:#00007F,bold',
56 'triple' : 'fore:#7F0000',
56 'triple' : 'fore:#7F0000',
57 'tripledouble' : 'fore:#7F0000',
57 'tripledouble' : 'fore:#7F0000',
58 'class' : 'fore:#0000FF,bold,underline',
58 'class' : 'fore:#0000FF,bold,underline',
59 'def' : 'fore:#007F7F,bold',
59 'def' : 'fore:#007F7F,bold',
60 'operator' : 'bold'
60 'operator' : 'bold'
61 }
61 }
62
62
63 # new style numbers
63 # new style numbers
64 _STDOUT_STYLE = 15
64 _STDOUT_STYLE = 15
65 _STDERR_STYLE = 16
65 _STDERR_STYLE = 16
66 _TRACE_STYLE = 17
66 _TRACE_STYLE = 17
67
67
68
68
69 # system colors
69 # system colors
70 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
70 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
71
71
72 #-------------------------------------------------------------------------------
72 #-------------------------------------------------------------------------------
73 # The console widget class
73 # The console widget class
74 #-------------------------------------------------------------------------------
74 #-------------------------------------------------------------------------------
75 class ConsoleWidget(editwindow.EditWindow):
75 class ConsoleWidget(editwindow.EditWindow):
76 """ Specialized styled text control view for console-like workflow.
76 """ Specialized styled text control view for console-like workflow.
77
77
78 This widget is mainly interested in dealing with the prompt and
78 This widget is mainly interested in dealing with the prompt and
79 keeping the cursor inside the editing line.
79 keeping the cursor inside the editing line.
80 """
80 """
81
81
82 # This is where the title captured from the ANSI escape sequences are
82 # This is where the title captured from the ANSI escape sequences are
83 # stored.
83 # stored.
84 title = 'Console'
84 title = 'Console'
85
85
86 # The buffer being edited.
86 # The buffer being edited.
87 def _set_input_buffer(self, string):
87 def _set_input_buffer(self, string):
88 self.SetSelection(self.current_prompt_pos, self.GetLength())
88 self.SetSelection(self.current_prompt_pos, self.GetLength())
89 self.ReplaceSelection(string)
89 self.ReplaceSelection(string)
90 self.GotoPos(self.GetLength())
90 self.GotoPos(self.GetLength())
91
91
92 def _get_input_buffer(self):
92 def _get_input_buffer(self):
93 """ Returns the text in current edit buffer.
93 """ Returns the text in current edit buffer.
94 """
94 """
95 input_buffer = self.GetTextRange(self.current_prompt_pos,
95 input_buffer = self.GetTextRange(self.current_prompt_pos,
96 self.GetLength())
96 self.GetLength())
97 input_buffer = input_buffer.replace(LINESEP, '\n')
97 input_buffer = input_buffer.replace(LINESEP, '\n')
98 return input_buffer
98 return input_buffer
99
99
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101
101
102 style = _DEFAULT_STYLE.copy()
102 style = _DEFAULT_STYLE.copy()
103
103
104 # Translation table from ANSI escape sequences to color. Override
104 # Translation table from ANSI escape sequences to color. Override
105 # this to specify your colors.
105 # this to specify your colors.
106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
107 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
107 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
108 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
108 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
109 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
109 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
110 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
110 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
111 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
111 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
112 '1;34': [12, 'LIGHT BLUE'], '1;35':
112 '1;34': [12, 'LIGHT BLUE'], '1;35':
113 [13, 'MEDIUM VIOLET RED'],
113 [13, 'MEDIUM VIOLET RED'],
114 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
114 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
115
115
116 # The color of the carret (call _apply_style() after setting)
116 # The color of the carret (call _apply_style() after setting)
117 carret_color = 'BLACK'
117 carret_color = 'BLACK'
118
118
119 # Store the last time a refresh was done
119 # Store the last time a refresh was done
120 _last_refresh_time = 0
120 _last_refresh_time = 0
121
121
122 #--------------------------------------------------------------------------
122 #--------------------------------------------------------------------------
123 # Public API
123 # Public API
124 #--------------------------------------------------------------------------
124 #--------------------------------------------------------------------------
125
125
126 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
126 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
129 self._configure_scintilla()
129 self._configure_scintilla()
130
130
131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
133
133
134
134
135 def write(self, text, refresh=True):
135 def write(self, text, refresh=True):
136 """ Write given text to buffer, while translating the ansi escape
136 """ Write given text to buffer, while translating the ansi escape
137 sequences.
137 sequences.
138 """
138 """
139 # XXX: do not put print statements to sys.stdout/sys.stderr in
139 # XXX: do not put print statements to sys.stdout/sys.stderr in
140 # this method, the print statements will call this method, as
140 # this method, the print statements will call this method, as
141 # you will end up with an infinit loop
141 # you will end up with an infinit loop
142 title = self.title_pat.split(text)
142 title = self.title_pat.split(text)
143 if len(title)>1:
143 if len(title)>1:
144 self.title = title[-2]
144 self.title = title[-2]
145
145
146 text = self.title_pat.sub('', text)
146 text = self.title_pat.sub('', text)
147 segments = self.color_pat.split(text)
147 segments = self.color_pat.split(text)
148 segment = segments.pop(0)
148 segment = segments.pop(0)
149 self.GotoPos(self.GetLength())
149 self.GotoPos(self.GetLength())
150 self.StartStyling(self.GetLength(), 0xFF)
150 self.StartStyling(self.GetLength(), 0xFF)
151 try:
151 try:
152 self.AppendText(segment)
152 self.AppendText(segment)
153 except UnicodeDecodeError:
153 except UnicodeDecodeError:
154 # XXX: Do I really want to skip the exception?
154 # XXX: Do I really want to skip the exception?
155 pass
155 pass
156
156
157 if segments:
157 if segments:
158 for ansi_tag, text in zip(segments[::2], segments[1::2]):
158 for ansi_tag, text in zip(segments[::2], segments[1::2]):
159 self.StartStyling(self.GetLength(), 0xFF)
159 self.StartStyling(self.GetLength(), 0xFF)
160 try:
160 try:
161 self.AppendText(text)
161 self.AppendText(text)
162 except UnicodeDecodeError:
162 except UnicodeDecodeError:
163 # XXX: Do I really want to skip the exception?
163 # XXX: Do I really want to skip the exception?
164 pass
164 pass
165
165
166 if ansi_tag not in self.ANSI_STYLES:
166 if ansi_tag not in self.ANSI_STYLES:
167 style = 0
167 style = 0
168 else:
168 else:
169 style = self.ANSI_STYLES[ansi_tag][0]
169 style = self.ANSI_STYLES[ansi_tag][0]
170
170
171 self.SetStyling(len(text), style)
171 self.SetStyling(len(text), style)
172
172
173 self.GotoPos(self.GetLength())
173 self.GotoPos(self.GetLength())
174 if refresh:
174 if refresh:
175 # Maybe this is faster than wx.Yield()
176 self.ProcessEvent(wx.PaintEvent())
177 current_time = time.time()
175 current_time = time.time()
178 if current_time - self._last_refresh_time > 0.03:
176 if current_time - self._last_refresh_time > 0.03:
179 wx.Yield()
177 # Maybe this is faster than wx.Yield(), this is certainly
178 # more robust under windows, as it avoids recursive
179 # Yields.
180 self.ProcessEvent(wx.PaintEvent())
180 self._last_refresh_time = current_time
181 self._last_refresh_time = current_time
181
182
182
183
183 def new_prompt(self, prompt):
184 def new_prompt(self, prompt):
184 """ Prints a prompt at start of line, and move the start of the
185 """ Prints a prompt at start of line, and move the start of the
185 current block there.
186 current block there.
186
187
187 The prompt can be given with ascii escape sequences.
188 The prompt can be given with ascii escape sequences.
188 """
189 """
189 self.write(prompt, refresh=False)
190 self.write(prompt, refresh=False)
190 # now we update our cursor giving end of prompt
191 # now we update our cursor giving end of prompt
191 self.current_prompt_pos = self.GetLength()
192 self.current_prompt_pos = self.GetLength()
192 self.current_prompt_line = self.GetCurrentLine()
193 self.current_prompt_line = self.GetCurrentLine()
193 wx.Yield()
194 wx.Yield()
194 self.EnsureCaretVisible()
195 self.EnsureCaretVisible()
195
196
196
197
197 def scroll_to_bottom(self):
198 def scroll_to_bottom(self):
198 maxrange = self.GetScrollRange(wx.VERTICAL)
199 maxrange = self.GetScrollRange(wx.VERTICAL)
199 self.ScrollLines(maxrange)
200 self.ScrollLines(maxrange)
200
201
201
202
202 def pop_completion(self, possibilities, offset=0):
203 def pop_completion(self, possibilities, offset=0):
203 """ Pops up an autocompletion menu. Offset is the offset
204 """ Pops up an autocompletion menu. Offset is the offset
204 in characters of the position at which the menu should
205 in characters of the position at which the menu should
205 appear, relativ to the cursor.
206 appear, relativ to the cursor.
206 """
207 """
207 self.AutoCompSetIgnoreCase(False)
208 self.AutoCompSetIgnoreCase(False)
208 self.AutoCompSetAutoHide(False)
209 self.AutoCompSetAutoHide(False)
209 self.AutoCompSetMaxHeight(len(possibilities))
210 self.AutoCompSetMaxHeight(len(possibilities))
210 self.AutoCompShow(offset, " ".join(possibilities))
211 self.AutoCompShow(offset, " ".join(possibilities))
211
212
212
213
213 def get_line_width(self):
214 def get_line_width(self):
214 """ Return the width of the line in characters.
215 """ Return the width of the line in characters.
215 """
216 """
216 return self.GetSize()[0]/self.GetCharWidth()
217 return self.GetSize()[0]/self.GetCharWidth()
217
218
218 #--------------------------------------------------------------------------
219 #--------------------------------------------------------------------------
219 # EditWindow API
220 # EditWindow API
220 #--------------------------------------------------------------------------
221 #--------------------------------------------------------------------------
221
222
222 def OnUpdateUI(self, event):
223 def OnUpdateUI(self, event):
223 """ Override the OnUpdateUI of the EditWindow class, to prevent
224 """ Override the OnUpdateUI of the EditWindow class, to prevent
224 syntax highlighting both for faster redraw, and for more
225 syntax highlighting both for faster redraw, and for more
225 consistent look and feel.
226 consistent look and feel.
226 """
227 """
227
228
228 #--------------------------------------------------------------------------
229 #--------------------------------------------------------------------------
229 # Private API
230 # Private API
230 #--------------------------------------------------------------------------
231 #--------------------------------------------------------------------------
231
232
232 def _apply_style(self):
233 def _apply_style(self):
233 """ Applies the colors for the different text elements and the
234 """ Applies the colors for the different text elements and the
234 carret.
235 carret.
235 """
236 """
236 self.SetCaretForeground(self.carret_color)
237 self.SetCaretForeground(self.carret_color)
237
238
238 #self.StyleClearAll()
239 #self.StyleClearAll()
239 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
240 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
240 "fore:#FF0000,back:#0000FF,bold")
241 "fore:#FF0000,back:#0000FF,bold")
241 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
242 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
242 "fore:#000000,back:#FF0000,bold")
243 "fore:#000000,back:#FF0000,bold")
243
244
244 for style in self.ANSI_STYLES.values():
245 for style in self.ANSI_STYLES.values():
245 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
246 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
246
247
247
248
248 def _configure_scintilla(self):
249 def _configure_scintilla(self):
249 self.SetEOLMode(stc.STC_EOL_LF)
250 self.SetEOLMode(stc.STC_EOL_LF)
250
251
251 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
252 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
252 # the widget
253 # the widget
253 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
254 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
254 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
255 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
255 # Also allow Ctrl Shift "=" for poor non US keyboard users.
256 # Also allow Ctrl Shift "=" for poor non US keyboard users.
256 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
257 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
257 stc.STC_CMD_ZOOMIN)
258 stc.STC_CMD_ZOOMIN)
258
259
259 # Keys: we need to clear some of the keys the that don't play
260 # Keys: we need to clear some of the keys the that don't play
260 # well with a console.
261 # well with a console.
261 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
262 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
262 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
263 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
263 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
264 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
264 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
265 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
265
266
266 self.SetEOLMode(stc.STC_EOL_CRLF)
267 self.SetEOLMode(stc.STC_EOL_CRLF)
267 self.SetWrapMode(stc.STC_WRAP_CHAR)
268 self.SetWrapMode(stc.STC_WRAP_CHAR)
268 self.SetWrapMode(stc.STC_WRAP_WORD)
269 self.SetWrapMode(stc.STC_WRAP_WORD)
269 self.SetBufferedDraw(True)
270 self.SetBufferedDraw(True)
270 self.SetUseAntiAliasing(True)
271 self.SetUseAntiAliasing(True)
271 self.SetLayoutCache(stc.STC_CACHE_PAGE)
272 self.SetLayoutCache(stc.STC_CACHE_PAGE)
272 self.SetUndoCollection(False)
273 self.SetUndoCollection(False)
273 self.SetUseTabs(True)
274 self.SetUseTabs(True)
274 self.SetIndent(4)
275 self.SetIndent(4)
275 self.SetTabWidth(4)
276 self.SetTabWidth(4)
276
277
277 # we don't want scintilla's autocompletion to choose
278 # we don't want scintilla's autocompletion to choose
278 # automaticaly out of a single choice list, as we pop it up
279 # automaticaly out of a single choice list, as we pop it up
279 # automaticaly
280 # automaticaly
280 self.AutoCompSetChooseSingle(False)
281 self.AutoCompSetChooseSingle(False)
281 self.AutoCompSetMaxHeight(10)
282 self.AutoCompSetMaxHeight(10)
282 # XXX: this doesn't seem to have an effect.
283 # XXX: this doesn't seem to have an effect.
283 self.AutoCompSetFillUps('\n')
284 self.AutoCompSetFillUps('\n')
284
285
285 self.SetMargins(3, 3) #text is moved away from border with 3px
286 self.SetMargins(3, 3) #text is moved away from border with 3px
286 # Suppressing Scintilla margins
287 # Suppressing Scintilla margins
287 self.SetMarginWidth(0, 0)
288 self.SetMarginWidth(0, 0)
288 self.SetMarginWidth(1, 0)
289 self.SetMarginWidth(1, 0)
289 self.SetMarginWidth(2, 0)
290 self.SetMarginWidth(2, 0)
290
291
291 self._apply_style()
292 self._apply_style()
292
293
293 # Xterm escape sequences
294 # Xterm escape sequences
294 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
295 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
295 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
296 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
296
297
297 #self.SetEdgeMode(stc.STC_EDGE_LINE)
298 #self.SetEdgeMode(stc.STC_EDGE_LINE)
298 #self.SetEdgeColumn(80)
299 #self.SetEdgeColumn(80)
299
300
300 # styles
301 # styles
301 p = self.style
302 p = self.style
302 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
303 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
303 self.StyleClearAll()
304 self.StyleClearAll()
304 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
305 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
305 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
306 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
306 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
307 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
307
308
308 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
309 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
309 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
310 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
310 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
311 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
311 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
312 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
312 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
313 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
313 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
314 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
314 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
315 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
315 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
316 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
316 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
317 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
317 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
318 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
318 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
319 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
319 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
320 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
320 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
321 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
321 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
322 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
322
323
323 def _on_key_down(self, event, skip=True):
324 def _on_key_down(self, event, skip=True):
324 """ Key press callback used for correcting behavior for
325 """ Key press callback used for correcting behavior for
325 console-like interfaces: the cursor is constraint to be after
326 console-like interfaces: the cursor is constraint to be after
326 the last prompt.
327 the last prompt.
327
328
328 Return True if event as been catched.
329 Return True if event as been catched.
329 """
330 """
330 catched = True
331 catched = True
331 # Intercept some specific keys.
332 # Intercept some specific keys.
332 if event.KeyCode == ord('L') and event.ControlDown() :
333 if event.KeyCode == ord('L') and event.ControlDown() :
333 self.scroll_to_bottom()
334 self.scroll_to_bottom()
334 elif event.KeyCode == ord('K') and event.ControlDown() :
335 elif event.KeyCode == ord('K') and event.ControlDown() :
335 self.input_buffer = ''
336 self.input_buffer = ''
336 elif event.KeyCode == ord('A') and event.ControlDown() :
337 elif event.KeyCode == ord('A') and event.ControlDown() :
337 self.GotoPos(self.GetLength())
338 self.GotoPos(self.GetLength())
338 self.SetSelectionStart(self.current_prompt_pos)
339 self.SetSelectionStart(self.current_prompt_pos)
339 self.SetSelectionEnd(self.GetCurrentPos())
340 self.SetSelectionEnd(self.GetCurrentPos())
340 catched = True
341 catched = True
341 elif event.KeyCode == ord('E') and event.ControlDown() :
342 elif event.KeyCode == ord('E') and event.ControlDown() :
342 self.GotoPos(self.GetLength())
343 self.GotoPos(self.GetLength())
343 catched = True
344 catched = True
344 elif event.KeyCode == wx.WXK_PAGEUP:
345 elif event.KeyCode == wx.WXK_PAGEUP:
345 self.ScrollPages(-1)
346 self.ScrollPages(-1)
346 elif event.KeyCode == wx.WXK_PAGEDOWN:
347 elif event.KeyCode == wx.WXK_PAGEDOWN:
347 self.ScrollPages(1)
348 self.ScrollPages(1)
348 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
349 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
349 self.ScrollLines(-1)
350 self.ScrollLines(-1)
350 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
351 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
351 self.ScrollLines(1)
352 self.ScrollLines(1)
352 else:
353 else:
353 catched = False
354 catched = False
354
355
355 if self.AutoCompActive():
356 if self.AutoCompActive():
356 event.Skip()
357 event.Skip()
357 else:
358 else:
358 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
359 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
359 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
360 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
360 catched = True
361 catched = True
361 self.CallTipCancel()
362 self.CallTipCancel()
362 self.write('\n', refresh=False)
363 self.write('\n', refresh=False)
363 # Under windows scintilla seems to be doing funny stuff to the
364 # Under windows scintilla seems to be doing funny stuff to the
364 # line returns here, but the getter for input_buffer filters
365 # line returns here, but the getter for input_buffer filters
365 # this out.
366 # this out.
366 if sys.platform == 'win32':
367 if sys.platform == 'win32':
367 self.input_buffer = self.input_buffer
368 self.input_buffer = self.input_buffer
368 self._on_enter()
369 self._on_enter()
369
370
370 elif event.KeyCode == wx.WXK_HOME:
371 elif event.KeyCode == wx.WXK_HOME:
371 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
372 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
372 self.GotoPos(self.current_prompt_pos)
373 self.GotoPos(self.current_prompt_pos)
373 catched = True
374 catched = True
374
375
375 elif event.Modifiers == wx.MOD_SHIFT:
376 elif event.Modifiers == wx.MOD_SHIFT:
376 # FIXME: This behavior is not ideal: if the selection
377 # FIXME: This behavior is not ideal: if the selection
377 # is already started, it will jump.
378 # is already started, it will jump.
378 self.SetSelectionStart(self.current_prompt_pos)
379 self.SetSelectionStart(self.current_prompt_pos)
379 self.SetSelectionEnd(self.GetCurrentPos())
380 self.SetSelectionEnd(self.GetCurrentPos())
380 catched = True
381 catched = True
381
382
382 elif event.KeyCode == wx.WXK_UP:
383 elif event.KeyCode == wx.WXK_UP:
383 if self.GetCurrentLine() > self.current_prompt_line:
384 if self.GetCurrentLine() > self.current_prompt_line:
384 if self.GetCurrentLine() == self.current_prompt_line + 1 \
385 if self.GetCurrentLine() == self.current_prompt_line + 1 \
385 and self.GetColumn(self.GetCurrentPos()) < \
386 and self.GetColumn(self.GetCurrentPos()) < \
386 self.GetColumn(self.current_prompt_pos):
387 self.GetColumn(self.current_prompt_pos):
387 self.GotoPos(self.current_prompt_pos)
388 self.GotoPos(self.current_prompt_pos)
388 else:
389 else:
389 event.Skip()
390 event.Skip()
390 catched = True
391 catched = True
391
392
392 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
393 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
393 if self.GetCurrentPos() > self.current_prompt_pos:
394 if self.GetCurrentPos() > self.current_prompt_pos:
394 event.Skip()
395 event.Skip()
395 catched = True
396 catched = True
396
397
397 if skip and not catched:
398 if skip and not catched:
398 # Put the cursor back in the edit region
399 # Put the cursor back in the edit region
399 if self.GetCurrentPos() < self.current_prompt_pos:
400 if self.GetCurrentPos() < self.current_prompt_pos:
400 self.GotoPos(self.current_prompt_pos)
401 self.GotoPos(self.current_prompt_pos)
401 else:
402 else:
402 event.Skip()
403 event.Skip()
403
404
404 return catched
405 return catched
405
406
406
407
407 def _on_key_up(self, event, skip=True):
408 def _on_key_up(self, event, skip=True):
408 """ If cursor is outside the editing region, put it back.
409 """ If cursor is outside the editing region, put it back.
409 """
410 """
410 event.Skip()
411 event.Skip()
411 if self.GetCurrentPos() < self.current_prompt_pos:
412 if self.GetCurrentPos() < self.current_prompt_pos:
412 self.GotoPos(self.current_prompt_pos)
413 self.GotoPos(self.current_prompt_pos)
413
414
414
415
415
416
416 if __name__ == '__main__':
417 if __name__ == '__main__':
417 # Some simple code to test the console widget.
418 # Some simple code to test the console widget.
418 class MainWindow(wx.Frame):
419 class MainWindow(wx.Frame):
419 def __init__(self, parent, id, title):
420 def __init__(self, parent, id, title):
420 wx.Frame.__init__(self, parent, id, title, size=(300,250))
421 wx.Frame.__init__(self, parent, id, title, size=(300,250))
421 self._sizer = wx.BoxSizer(wx.VERTICAL)
422 self._sizer = wx.BoxSizer(wx.VERTICAL)
422 self.console_widget = ConsoleWidget(self)
423 self.console_widget = ConsoleWidget(self)
423 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
424 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
424 self.SetSizer(self._sizer)
425 self.SetSizer(self._sizer)
425 self.SetAutoLayout(1)
426 self.SetAutoLayout(1)
426 self.Show(True)
427 self.Show(True)
427
428
428 app = wx.PySimpleApp()
429 app = wx.PySimpleApp()
429 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
430 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
430 w.SetSize((780, 460))
431 w.SetSize((780, 460))
431 w.Show()
432 w.Show()
432
433
433 app.MainLoop()
434 app.MainLoop()
434
435
435
436
General Comments 0
You need to be logged in to leave comments. Login now