##// END OF EJS Templates
ENH: Better handling of continuation lines
Gael Varoquaux -
Show More
@@ -1,482 +1,507 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_SIZE = 12
39 _DEFAULT_SIZE = 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 current_time = time.time()
175 current_time = time.time()
176 if current_time - self._last_refresh_time > 0.03:
176 if current_time - self._last_refresh_time > 0.03:
177 if sys.platform == 'win32':
177 if sys.platform == 'win32':
178 wx.SafeYield()
178 wx.SafeYield()
179 else:
179 else:
180 wx.Yield()
180 wx.Yield()
181 # self.ProcessEvent(wx.PaintEvent())
181 # self.ProcessEvent(wx.PaintEvent())
182 self._last_refresh_time = current_time
182 self._last_refresh_time = current_time
183
183
184
184
185 def new_prompt(self, prompt):
185 def new_prompt(self, prompt):
186 """ Prints a prompt at start of line, and move the start of the
186 """ Prints a prompt at start of line, and move the start of the
187 current block there.
187 current block there.
188
188
189 The prompt can be given with ascii escape sequences.
189 The prompt can be given with ascii escape sequences.
190 """
190 """
191 self.write(prompt, refresh=False)
191 self.write(prompt, refresh=False)
192 # now we update our cursor giving end of prompt
192 # now we update our cursor giving end of prompt
193 self.current_prompt_pos = self.GetLength()
193 self.current_prompt_pos = self.GetLength()
194 self.current_prompt_line = self.GetCurrentLine()
194 self.current_prompt_line = self.GetCurrentLine()
195 self.EnsureCaretVisible()
195 self.EnsureCaretVisible()
196
196
197
197
198 def scroll_to_bottom(self):
198 def scroll_to_bottom(self):
199 maxrange = self.GetScrollRange(wx.VERTICAL)
199 maxrange = self.GetScrollRange(wx.VERTICAL)
200 self.ScrollLines(maxrange)
200 self.ScrollLines(maxrange)
201
201
202
202
203 def pop_completion(self, possibilities, offset=0):
203 def pop_completion(self, possibilities, offset=0):
204 """ Pops up an autocompletion menu. Offset is the offset
204 """ Pops up an autocompletion menu. Offset is the offset
205 in characters of the position at which the menu should
205 in characters of the position at which the menu should
206 appear, relativ to the cursor.
206 appear, relativ to the cursor.
207 """
207 """
208 self.AutoCompSetIgnoreCase(False)
208 self.AutoCompSetIgnoreCase(False)
209 self.AutoCompSetAutoHide(False)
209 self.AutoCompSetAutoHide(False)
210 self.AutoCompSetMaxHeight(len(possibilities))
210 self.AutoCompSetMaxHeight(len(possibilities))
211 self.AutoCompShow(offset, " ".join(possibilities))
211 self.AutoCompShow(offset, " ".join(possibilities))
212
212
213
213
214 def get_line_width(self):
214 def get_line_width(self):
215 """ Return the width of the line in characters.
215 """ Return the width of the line in characters.
216 """
216 """
217 return self.GetSize()[0]/self.GetCharWidth()
217 return self.GetSize()[0]/self.GetCharWidth()
218
218
219
219
220 def clear_screen(self):
220 def clear_screen(self):
221 """ Empty completely the widget.
221 """ Empty completely the widget.
222 """
222 """
223 self.ClearAll()
223 self.ClearAll()
224 self.new_prompt(self.input_prompt_template.substitute(
224 self.new_prompt(self.input_prompt_template.substitute(
225 number=(self.last_result['number'] + 1)))
225 number=(self.last_result['number'] + 1)))
226
226
227
227
228
228
229 #--------------------------------------------------------------------------
229 #--------------------------------------------------------------------------
230 # EditWindow API
230 # EditWindow API
231 #--------------------------------------------------------------------------
231 #--------------------------------------------------------------------------
232
232
233 def OnUpdateUI(self, event):
233 def OnUpdateUI(self, event):
234 """ Override the OnUpdateUI of the EditWindow class, to prevent
234 """ Override the OnUpdateUI of the EditWindow class, to prevent
235 syntax highlighting both for faster redraw, and for more
235 syntax highlighting both for faster redraw, and for more
236 consistent look and feel.
236 consistent look and feel.
237 """
237 """
238
238
239 #--------------------------------------------------------------------------
239 #--------------------------------------------------------------------------
240 # Private API
240 # Private API
241 #--------------------------------------------------------------------------
241 #--------------------------------------------------------------------------
242
242
243 def _apply_style(self):
243 def _apply_style(self):
244 """ Applies the colors for the different text elements and the
244 """ Applies the colors for the different text elements and the
245 carret.
245 carret.
246 """
246 """
247 self.SetCaretForeground(self.carret_color)
247 self.SetCaretForeground(self.carret_color)
248
248
249 #self.StyleClearAll()
249 #self.StyleClearAll()
250 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
250 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
251 "fore:#FF0000,back:#0000FF,bold")
251 "fore:#FF0000,back:#0000FF,bold")
252 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
252 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
253 "fore:#000000,back:#FF0000,bold")
253 "fore:#000000,back:#FF0000,bold")
254
254
255 for style in self.ANSI_STYLES.values():
255 for style in self.ANSI_STYLES.values():
256 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
256 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
257
257
258
258
259 def _configure_scintilla(self):
259 def _configure_scintilla(self):
260 self.SetEOLMode(stc.STC_EOL_LF)
260 self.SetEOLMode(stc.STC_EOL_LF)
261
261
262 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
262 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
263 # the widget
263 # the widget
264 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
264 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
265 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
265 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
266 # Also allow Ctrl Shift "=" for poor non US keyboard users.
266 # Also allow Ctrl Shift "=" for poor non US keyboard users.
267 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
267 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
268 stc.STC_CMD_ZOOMIN)
268 stc.STC_CMD_ZOOMIN)
269
269
270 # Keys: we need to clear some of the keys the that don't play
270 # Keys: we need to clear some of the keys the that don't play
271 # well with a console.
271 # well with a console.
272 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
272 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
273 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
273 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
274 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
274 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
275 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
275 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
276
276
277 self.SetEOLMode(stc.STC_EOL_CRLF)
277 self.SetEOLMode(stc.STC_EOL_CRLF)
278 self.SetWrapMode(stc.STC_WRAP_CHAR)
278 self.SetWrapMode(stc.STC_WRAP_CHAR)
279 self.SetWrapMode(stc.STC_WRAP_WORD)
279 self.SetWrapMode(stc.STC_WRAP_WORD)
280 self.SetBufferedDraw(True)
280 self.SetBufferedDraw(True)
281 self.SetUseAntiAliasing(True)
281 self.SetUseAntiAliasing(True)
282 self.SetLayoutCache(stc.STC_CACHE_PAGE)
282 self.SetLayoutCache(stc.STC_CACHE_PAGE)
283 self.SetUndoCollection(False)
283 self.SetUndoCollection(False)
284 self.SetUseTabs(True)
284 self.SetUseTabs(True)
285 self.SetIndent(4)
285 self.SetIndent(4)
286 self.SetTabWidth(4)
286 self.SetTabWidth(4)
287
287
288 # we don't want scintilla's autocompletion to choose
288 # we don't want scintilla's autocompletion to choose
289 # automaticaly out of a single choice list, as we pop it up
289 # automaticaly out of a single choice list, as we pop it up
290 # automaticaly
290 # automaticaly
291 self.AutoCompSetChooseSingle(False)
291 self.AutoCompSetChooseSingle(False)
292 self.AutoCompSetMaxHeight(10)
292 self.AutoCompSetMaxHeight(10)
293 # XXX: this doesn't seem to have an effect.
293 # XXX: this doesn't seem to have an effect.
294 self.AutoCompSetFillUps('\n')
294 self.AutoCompSetFillUps('\n')
295
295
296 self.SetMargins(3, 3) #text is moved away from border with 3px
296 self.SetMargins(3, 3) #text is moved away from border with 3px
297 # Suppressing Scintilla margins
297 # Suppressing Scintilla margins
298 self.SetMarginWidth(0, 0)
298 self.SetMarginWidth(0, 0)
299 self.SetMarginWidth(1, 0)
299 self.SetMarginWidth(1, 0)
300 self.SetMarginWidth(2, 0)
300 self.SetMarginWidth(2, 0)
301
301
302 self._apply_style()
302 self._apply_style()
303
303
304 # Xterm escape sequences
304 # Xterm escape sequences
305 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
305 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
306 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
306 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
307
307
308 #self.SetEdgeMode(stc.STC_EDGE_LINE)
308 #self.SetEdgeMode(stc.STC_EDGE_LINE)
309 #self.SetEdgeColumn(80)
309 #self.SetEdgeColumn(80)
310
310
311 # styles
311 # styles
312 p = self.style
312 p = self.style
313 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
313 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
314 self.StyleClearAll()
314 self.StyleClearAll()
315 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
315 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
316 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
316 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
317 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
317 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
318
318
319 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
319 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
320 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
320 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
321 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
321 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
322 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
322 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
323 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
323 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
324 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
324 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
325 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
325 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
326 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
326 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
327 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
327 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
328 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
328 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
329 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
329 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
330 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
330 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
331 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
331 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
332 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
332 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
333
333
334 def _on_key_down(self, event, skip=True):
334 def _on_key_down(self, event, skip=True):
335 """ Key press callback used for correcting behavior for
335 """ Key press callback used for correcting behavior for
336 console-like interfaces: the cursor is constraint to be after
336 console-like interfaces: the cursor is constraint to be after
337 the last prompt.
337 the last prompt.
338
338
339 Return True if event as been catched.
339 Return True if event as been catched.
340 """
340 """
341 catched = True
341 catched = True
342 # Intercept some specific keys.
342 # Intercept some specific keys.
343 if event.KeyCode == ord('L') and event.ControlDown() :
343 if event.KeyCode == ord('L') and event.ControlDown() :
344 self.scroll_to_bottom()
344 self.scroll_to_bottom()
345 elif event.KeyCode == ord('K') and event.ControlDown() :
345 elif event.KeyCode == ord('K') and event.ControlDown() :
346 self.input_buffer = ''
346 self.input_buffer = ''
347 elif event.KeyCode == ord('A') and event.ControlDown() :
347 elif event.KeyCode == ord('A') and event.ControlDown() :
348 self.GotoPos(self.GetLength())
348 self.GotoPos(self.GetLength())
349 self.SetSelectionStart(self.current_prompt_pos)
349 self.SetSelectionStart(self.current_prompt_pos)
350 self.SetSelectionEnd(self.GetCurrentPos())
350 self.SetSelectionEnd(self.GetCurrentPos())
351 catched = True
351 catched = True
352 elif event.KeyCode == ord('E') and event.ControlDown() :
352 elif event.KeyCode == ord('E') and event.ControlDown() :
353 self.GotoPos(self.GetLength())
353 self.GotoPos(self.GetLength())
354 catched = True
354 catched = True
355 elif event.KeyCode == wx.WXK_PAGEUP:
355 elif event.KeyCode == wx.WXK_PAGEUP:
356 self.ScrollPages(-1)
356 self.ScrollPages(-1)
357 elif event.KeyCode == wx.WXK_PAGEDOWN:
357 elif event.KeyCode == wx.WXK_PAGEDOWN:
358 self.ScrollPages(1)
358 self.ScrollPages(1)
359 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
359 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
360 self.ScrollLines(-1)
360 self.ScrollLines(-1)
361 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
361 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
362 self.ScrollLines(1)
362 self.ScrollLines(1)
363 else:
363 else:
364 catched = False
364 catched = False
365
365
366 if self.AutoCompActive():
366 if self.AutoCompActive():
367 event.Skip()
367 event.Skip()
368 else:
368 else:
369 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
369 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
370 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
370 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
371 catched = True
371 catched = True
372 self.CallTipCancel()
372 self.CallTipCancel()
373 self.write('\n', refresh=False)
373 self.write('\n', refresh=False)
374 # Under windows scintilla seems to be doing funny stuff to the
374 # Under windows scintilla seems to be doing funny stuff to the
375 # line returns here, but the getter for input_buffer filters
375 # line returns here, but the getter for input_buffer filters
376 # this out.
376 # this out.
377 if sys.platform == 'win32':
377 if sys.platform == 'win32':
378 self.input_buffer = self.input_buffer
378 self.input_buffer = self.input_buffer
379 self._on_enter()
379 self._on_enter()
380
380
381 elif event.KeyCode == wx.WXK_HOME:
381 elif event.KeyCode == wx.WXK_HOME:
382 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
382 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
383 self.GotoPos(self.current_prompt_pos)
383 self.GotoPos(self.current_prompt_pos)
384 catched = True
384 catched = True
385
385
386 elif event.Modifiers == wx.MOD_SHIFT:
386 elif event.Modifiers == wx.MOD_SHIFT:
387 # FIXME: This behavior is not ideal: if the selection
387 # FIXME: This behavior is not ideal: if the selection
388 # is already started, it will jump.
388 # is already started, it will jump.
389 self.SetSelectionStart(self.current_prompt_pos)
389 self.SetSelectionStart(self.current_prompt_pos)
390 self.SetSelectionEnd(self.GetCurrentPos())
390 self.SetSelectionEnd(self.GetCurrentPos())
391 catched = True
391 catched = True
392
392
393 elif event.KeyCode == wx.WXK_UP:
393 elif event.KeyCode == wx.WXK_UP:
394 if self.GetCurrentLine() > self.current_prompt_line:
394 if self.GetCurrentLine() > self.current_prompt_line:
395 if self.GetCurrentLine() == self.current_prompt_line + 1 \
395 if self.GetCurrentLine() == self.current_prompt_line + 1 \
396 and self.GetColumn(self.GetCurrentPos()) < \
396 and self.GetColumn(self.GetCurrentPos()) < \
397 self.GetColumn(self.current_prompt_pos):
397 self.GetColumn(self.current_prompt_pos):
398 self.GotoPos(self.current_prompt_pos)
398 self.GotoPos(self.current_prompt_pos)
399 else:
399 else:
400 event.Skip()
400 event.Skip()
401 catched = True
401 catched = True
402
402
403 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
403 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
404 if not self._keep_cursor_in_buffer():
404 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
405 event.Skip()
406 catched = True
407
408 elif event.KeyCode == wx.WXK_RIGHT:
409 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
410 event.Skip()
411 catched = True
412
413
414 elif event.KeyCode == wx.WXK_DELETE:
415 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
405 event.Skip()
416 event.Skip()
406 catched = True
417 catched = True
407
418
408 if skip and not catched:
419 if skip and not catched:
409 # Put the cursor back in the edit region
420 # Put the cursor back in the edit region
410 if not self._keep_cursor_in_buffer():
421 if not self._keep_cursor_in_buffer():
411 if (self.GetCurrentPos() == self.GetLength()
422 if not (self.GetCurrentPos() == self.GetLength()
412 and event.KeyCode == wx.WXK_DELETE):
423 and event.KeyCode == wx.WXK_DELETE):
413 pass
424 event.Skip()
414 event.Skip()
425 catched = True
415
426
416 return catched
427 return catched
417
428
418
429
419 def _on_key_up(self, event, skip=True):
430 def _on_key_up(self, event, skip=True):
420 """ If cursor is outside the editing region, put it back.
431 """ If cursor is outside the editing region, put it back.
421 """
432 """
422 if skip:
433 if skip:
423 event.Skip()
434 event.Skip()
424 self._keep_cursor_in_buffer()
435 self._keep_cursor_in_buffer()
425
436
426
437
427 def _keep_cursor_in_buffer(self):
438 def _keep_cursor_in_buffer(self, pos=None):
428 """ Checks if the cursor is where it is allowed to be. If not,
439 """ Checks if the cursor is where it is allowed to be. If not,
429 put it back.
440 put it back.
430
441
431 Returns
442 Returns
432 -------
443 -------
433 cursor_moved: Boolean
444 cursor_moved: Boolean
434 whether or not the cursor was moved by this routine.
445 whether or not the cursor was moved by this routine.
435
446
436 Notes
447 Notes
437 ------
448 ------
438 WARNING: This does proper checks only for horizontal
449 WARNING: This does proper checks only for horizontal
439 movements.
450 movements.
440 """
451 """
441 current_pos = self.GetCurrentPos()
452 if pos is None:
453 current_pos = self.GetCurrentPos()
454 else:
455 current_pos = pos
442 if current_pos < self.current_prompt_pos:
456 if current_pos < self.current_prompt_pos:
443 self.GotoPos(self.current_prompt_pos)
457 self.GotoPos(self.current_prompt_pos)
444 return True
458 return True
445 line, line_pos = self.GetCurLine()
459 line_num = self.LineFromPosition(current_pos)
460 if not current_pos > self.GetLength():
461 line_pos = self.GetColumn(current_pos)
462 else:
463 line_pos = self.GetColumn(self.GetLength())
464 line = self.GetLine(line_num)
446 # Jump the continuation prompt
465 # Jump the continuation prompt
447 continuation_prompt = self.continuation_prompt()
466 continuation_prompt = self.continuation_prompt()
448 if ( line.startswith(continuation_prompt)
467 if ( line.startswith(continuation_prompt)
449 and line_pos < len(continuation_prompt)+1):
468 and line_pos < len(continuation_prompt)+1):
450 if line_pos < 2:
469 if line_pos < 2:
451 # We are at the beginning of the line, trying to move
470 # We are at the beginning of the line, trying to move
452 # forward: jump forward.
471 # forward: jump forward.
453 self.GotoPos(current_pos + 1 +
472 self.GotoPos(current_pos + 1 +
454 len(continuation_prompt) - line_pos)
473 len(continuation_prompt) - line_pos)
455 else:
474 else:
456 # Jump back up
475 # Jump back up
457 self.GotoPos(self.GetLineEndPosition(self.GetCurrentLine()-1))
476 self.GotoPos(self.GetLineEndPosition(line_num-1))
477 return True
478 elif ( current_pos > self.GetLineEndPosition(line_num)
479 and not current_pos == self.GetLength()):
480 # Jump to next line
481 self.GotoPos(current_pos + 1 +
482 len(continuation_prompt))
458 return True
483 return True
459 return False
484 return False
460
485
461
486
462
487
463 if __name__ == '__main__':
488 if __name__ == '__main__':
464 # Some simple code to test the console widget.
489 # Some simple code to test the console widget.
465 class MainWindow(wx.Frame):
490 class MainWindow(wx.Frame):
466 def __init__(self, parent, id, title):
491 def __init__(self, parent, id, title):
467 wx.Frame.__init__(self, parent, id, title, size=(300,250))
492 wx.Frame.__init__(self, parent, id, title, size=(300,250))
468 self._sizer = wx.BoxSizer(wx.VERTICAL)
493 self._sizer = wx.BoxSizer(wx.VERTICAL)
469 self.console_widget = ConsoleWidget(self)
494 self.console_widget = ConsoleWidget(self)
470 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
495 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
471 self.SetSizer(self._sizer)
496 self.SetSizer(self._sizer)
472 self.SetAutoLayout(1)
497 self.SetAutoLayout(1)
473 self.Show(True)
498 self.Show(True)
474
499
475 app = wx.PySimpleApp()
500 app = wx.PySimpleApp()
476 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
501 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
477 w.SetSize((780, 460))
502 w.SetSize((780, 460))
478 w.Show()
503 w.Show()
479
504
480 app.MainLoop()
505 app.MainLoop()
481
506
482
507
General Comments 0
You need to be logged in to leave comments. Login now