##// END OF EJS Templates
Nice background color for already entered code....
Gael Varoquaux -
Show More
@@ -1,162 +1,162 b''
1 1 """
2 2 Base front end class for all line-oriented frontends.
3 3
4 4 Currently this focuses on synchronous frontends.
5 5 """
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 #-------------------------------------------------------------------------------
16 16 # Imports
17 17 #-------------------------------------------------------------------------------
18 18 import re
19 19
20 20 import IPython
21 21
22 22
23 23 from frontendbase import FrontEndBase
24 24 from IPython.kernel.core.interpreter import Interpreter
25 25
26 26 #-------------------------------------------------------------------------------
27 27 # Base class for the line-oriented front ends
28 28 #-------------------------------------------------------------------------------
29 29 class LineFrontEndBase(FrontEndBase):
30 30
31 31 # We need to keep the prompt number, to be able to increment
32 32 # it when there is an exception.
33 33 prompt_number = 1
34 34
35 35 # To bootstrap
36 36 last_result = dict(number=0)
37 37
38 38 #--------------------------------------------------------------------------
39 39 # Public API
40 40 #--------------------------------------------------------------------------
41 41
42 42 def __init__(self, shell=None, history=None):
43 43 if shell is None:
44 44 shell = Interpreter()
45 45 FrontEndBase.__init__(self, shell=shell, history=history)
46 46
47 47 #FIXME: print banner.
48 48 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
49 49 % IPython.__version__
50 50
51 51
52 52 def complete(self, token):
53 53 """Complete token in engine's user_ns
54 54
55 55 Parameters
56 56 ----------
57 57 token : string
58 58
59 59 Result
60 60 ------
61 61 Deferred result of
62 62 IPython.kernel.engineservice.IEngineBase.complete
63 63 """
64 64
65 65 return self.shell.complete(token)
66 66
67 67
68 68 def render_result(self, result):
69 69 if 'stdout' in result and result['stdout']:
70 70 self.write('\n' + result['stdout'])
71 71 if 'display' in result and result['display']:
72 72 self.write("%s%s\n" % (
73 73 self.output_prompt % result['number'],
74 74 result['display']['pprint']
75 75 ) )
76 76
77 77
78 78 def render_error(self, failure):
79 79 self.insert_text('\n\n'+str(failure)+'\n\n')
80 80 return failure
81 81
82 82
83 83 def prefilter_input(self, string):
84 84 string = string.replace('\r\n', '\n')
85 85 string = string.replace('\t', 4*' ')
86 86 # Clean the trailing whitespace
87 87 string = '\n'.join(l.rstrip() for l in string.split('\n'))
88 88 return string
89 89
90 90
91 91 def is_complete(self, string):
92 92 if string in ('', '\n'):
93 93 return True
94 94 elif ( len(self.get_current_edit_buffer().split('\n'))>2
95 95 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
96 96 return False
97 97 else:
98 98 # Add line returns here, to make sure that the statement is
99 99 # complete.
100 100 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
101 101
102 102
103 103 def execute(self, python_string, raw_string=None):
104 104 """ Send the python_string to the interpreter, stores the
105 105 raw_string in the history and starts a new prompt.
106 106 """
107 107 if raw_string is None:
108 raw_string = string
108 raw_string = python_string
109 109 # Create a false result, in case there is an exception
110 110 self.last_result = dict(number=self.prompt_number)
111 111 try:
112 112 self.history.input_cache[-1] = raw_string.rstrip()
113 113 result = self.shell.execute(python_string)
114 114 self.last_result = result
115 115 self.render_result(result)
116 116 except:
117 117 self.show_traceback()
118 118 finally:
119 119 self.after_execute()
120 120
121 121
122 122 def after_execute(self):
123 123 """ All the operations required after an execution to put the
124 124 terminal back in a shape where it is usable.
125 125 """
126 126 self.prompt_number += 1
127 127 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
128 128 # Start a new empty history entry
129 129 self._add_history(None, '')
130 130 self.history_cursor = len(self.history.input_cache) - 1
131 131
132 132
133 133 def _on_enter(self):
134 134 """ Called when the return key is pressed in a line editing
135 135 buffer.
136 136 """
137 137 current_buffer = self.get_current_edit_buffer()
138 138 cleaned_buffer = self.prefilter_input(current_buffer)
139 139 if self.is_complete(cleaned_buffer):
140 140 self.execute(cleaned_buffer, raw_string=current_buffer)
141 141 else:
142 142 if len(current_buffer.split('\n'))>2:
143 143 # We need to clean the trailing '\n'
144 144 self.write(self._get_indent_string(current_buffer[:-1]))
145 145 else:
146 146 self.write('\t')
147 147
148 148
149 149 #--------------------------------------------------------------------------
150 150 # Private API
151 151 #--------------------------------------------------------------------------
152 152
153 153 def _get_indent_string(self, string):
154 154 string = string.replace('\t', ' '*4)
155 155 string = string.split('\n')[-1]
156 156 indent_chars = len(string) - len(string.lstrip())
157 157 indent_string = '\t'*(indent_chars // 4) + \
158 158 ' '*(indent_chars % 4)
159 159
160 160 return indent_string
161 161
162 162
@@ -1,406 +1,406 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26
27 27 import re
28 28
29 29 # FIXME: Need to provide an API for non user-generated display on the
30 30 # screen: this should not be editable by the user.
31 31
32 32 if wx.Platform == '__WXMSW__':
33 33 _DEFAULT_SIZE = 80
34 34 else:
35 35 _DEFAULT_SIZE = 10
36 36
37 37 _DEFAULT_STYLE = {
38 38 'stdout' : 'fore:#0000FF',
39 39 'stderr' : 'fore:#007f00',
40 40 'trace' : 'fore:#FF0000',
41 41
42 42 'default' : 'size:%d' % _DEFAULT_SIZE,
43 43 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
44 44 'bracebad' : 'fore:#000000,back:#FF0000,bold',
45 45
46 46 # properties for the various Python lexer styles
47 'comment' : 'fore:#007F00',
48 'number' : 'fore:#007F7F',
49 'string' : 'fore:#7F007F,italic',
50 'char' : 'fore:#7F007F,italic',
51 'keyword' : 'fore:#00007F,bold',
52 'triple' : 'fore:#7F0000',
53 'tripledouble': 'fore:#7F0000',
54 'class' : 'fore:#0000FF,bold,underline',
55 'def' : 'fore:#007F7F,bold',
56 'operator' : 'bold',
57
47 'comment' : 'fore:#007F00',
48 'number' : 'fore:#007F7F',
49 'string' : 'fore:#7F007F,italic',
50 'char' : 'fore:#7F007F,italic',
51 'keyword' : 'fore:#00007F,bold',
52 'triple' : 'fore:#7F0000',
53 'tripledouble' : 'fore:#7F0000',
54 'class' : 'fore:#0000FF,bold,underline',
55 'def' : 'fore:#007F7F,bold',
56 'operator' : 'bold'
58 57 }
59 58
60 59 # new style numbers
61 60 _STDOUT_STYLE = 15
62 61 _STDERR_STYLE = 16
63 62 _TRACE_STYLE = 17
64 63
65 64
66 65 # system colors
67 66 SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
68 67
69 68 #-------------------------------------------------------------------------------
70 69 # The console widget class
71 70 #-------------------------------------------------------------------------------
72 71 class ConsoleWidget(editwindow.EditWindow):
73 72 """ Specialized styled text control view for console-like workflow.
74 73
75 74 This widget is mainly interested in dealing with the prompt and
76 75 keeping the cursor inside the editing line.
77 76 """
78 77
79 78 title = 'Console'
80 79
81 80 style = _DEFAULT_STYLE.copy()
82 81
83 82 # Translation table from ANSI escape sequences to color. Override
84 83 # this to specify your colors.
85 84 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
86 85 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
87 86 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
88 87 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
89 88 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
90 89 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
91 90 '1;34': [12, 'LIGHT BLUE'], '1;35':
92 91 [13, 'MEDIUM VIOLET RED'],
93 92 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
94 93
95 94 # The color of the carret (call _apply_style() after setting)
96 95 carret_color = 'BLACK'
97 96
98 97
99 98 #--------------------------------------------------------------------------
100 99 # Public API
101 100 #--------------------------------------------------------------------------
102 101
103 102 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
104 103 size=wx.DefaultSize, style=0,
105 104 autocomplete_mode='popup'):
106 105 """ Autocomplete_mode: Can be 'popup' or 'text'
107 106 'text' show autocompletion in the text buffer
108 107 'popup' show it with a dropdown popup
109 108 """
110 109 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
111 110 self.configure_scintilla()
112 111
113 112 # FIXME: we need to retrieve this from the interpreter.
114 113 self.prompt = \
115 114 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
116 115 self.new_prompt(self.prompt % 1)
117 116
118 117 self.autocomplete_mode = autocomplete_mode
119 118
120 119 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
121 120 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
122 121
123 122
124 123 def configure_scintilla(self):
125 124 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
126 125 # the widget
127 126 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
128 127 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
129 128 # Also allow Ctrl Shift "=" for poor non US keyboard users.
130 129 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
131 130 stc.STC_CMD_ZOOMIN)
132 131
133 132 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
134 133 # stc.STC_CMD_PAGEUP)
135 134
136 135 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
137 136 # stc.STC_CMD_PAGEDOWN)
138 137
139 138 # Keys: we need to clear some of the keys the that don't play
140 139 # well with a console.
141 140 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
142 141 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
143 142 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
144 143
145 144
146 145 self.SetEOLMode(stc.STC_EOL_CRLF)
147 146 self.SetWrapMode(stc.STC_WRAP_CHAR)
148 147 self.SetWrapMode(stc.STC_WRAP_WORD)
149 148 self.SetBufferedDraw(True)
150 149 self.SetUseAntiAliasing(True)
151 150 self.SetLayoutCache(stc.STC_CACHE_PAGE)
152 151 self.SetUndoCollection(False)
153 152 self.SetUseTabs(True)
154 153 self.SetIndent(4)
155 154 self.SetTabWidth(4)
156 155
157 156 self.EnsureCaretVisible()
158 157 # Tell autocompletion to choose automaticaly out of a single
159 158 # choice list
160 159 self.AutoCompSetChooseSingle(True)
161 160 self.AutoCompSetMaxHeight(10)
162 161
163 162 self.SetMargins(3, 3) #text is moved away from border with 3px
164 163 # Suppressing Scintilla margins
165 164 self.SetMarginWidth(0, 0)
166 165 self.SetMarginWidth(1, 0)
167 166 self.SetMarginWidth(2, 0)
168 167
169 168 self._apply_style()
170 169
171 170 # Xterm escape sequences
172 171 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
173 172 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
174 173
175 174 #self.SetEdgeMode(stc.STC_EDGE_LINE)
176 175 #self.SetEdgeColumn(80)
177 176
178 177 # styles
179 178 p = self.style
180 179 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
181 180 self.StyleClearAll()
182 181 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
183 182 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
184 183 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
185 184
186 185 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
187 186 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
188 187 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
189 188 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
190 189 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
191 190 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
192 191 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
192 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
193 193 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
194 194 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
195 195 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
196 196 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
197 197 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
198 198 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
199 199
200 200
201 201 def write(self, text):
202 202 """ Write given text to buffer, while translating the ansi escape
203 203 sequences.
204 204 """
205 205 title = self.title_pat.split(text)
206 206 if len(title)>0:
207 207 self.title = title[-1]
208 208
209 209 text = self.title_pat.sub('', text)
210 210 segments = self.color_pat.split(text)
211 211 segment = segments.pop(0)
212 212 self.StartStyling(self.GetLength(), 0xFF)
213 213 self.AppendText(segment)
214 214
215 215 if segments:
216 216 ansi_tags = self.color_pat.findall(text)
217 217
218 218 for tag in ansi_tags:
219 219 i = segments.index(tag)
220 220 self.StartStyling(self.GetLength(), 0xFF)
221 221 self.AppendText(segments[i+1])
222 222
223 223 if tag != '0':
224 224 self.SetStyling(len(segments[i+1]),
225 225 self.ANSI_STYLES[tag][0])
226 226
227 227 segments.pop(i)
228 228
229 229 self.GotoPos(self.GetLength())
230 230
231 231
232 232 def new_prompt(self, prompt):
233 233 """ Prints a prompt at start of line, and move the start of the
234 234 current block there.
235 235
236 236 The prompt can be give with ascii escape sequences.
237 237 """
238 238 self.write(prompt)
239 239 # now we update our cursor giving end of prompt
240 240 self.current_prompt_pos = self.GetLength()
241 241 self.current_prompt_line = self.GetCurrentLine()
242 242 wx.Yield()
243 243 self.EnsureCaretVisible()
244 244
245 245
246 246 def replace_current_edit_buffer(self, text):
247 247 """ Replace currently entered command line with given text.
248 248 """
249 249 self.SetSelection(self.current_prompt_pos, self.GetLength())
250 250 self.ReplaceSelection(text)
251 251 self.GotoPos(self.GetLength())
252 252
253 253
254 254 def get_current_edit_buffer(self):
255 255 """ Returns the text in current edit buffer.
256 256 """
257 257 return self.GetTextRange(self.current_prompt_pos,
258 258 self.GetLength())
259 259
260 260
261 261 #--------------------------------------------------------------------------
262 262 # Private API
263 263 #--------------------------------------------------------------------------
264 264
265 265 def _apply_style(self):
266 266 """ Applies the colors for the different text elements and the
267 267 carret.
268 268 """
269 269 self.SetCaretForeground(self.carret_color)
270 270
271 271 #self.StyleClearAll()
272 272 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
273 273 "fore:#FF0000,back:#0000FF,bold")
274 274 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
275 275 "fore:#000000,back:#FF0000,bold")
276 276
277 277 for style in self.ANSI_STYLES.values():
278 278 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
279 279
280 280
281 281 def write_completion(self, possibilities, mode=None):
282 282 if mode=='text' or self.autocomplete_mode == 'text':
283 283 max_len = len(max(possibilities, key=len))
284 284 current_buffer = self.get_current_edit_buffer()
285 285
286 286 self.write('\n')
287 287 for symbol in possibilities:
288 288 self.write(symbol.ljust(max_len))
289 289 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
290 290 self.replace_current_edit_buffer(current_buffer)
291 291 else:
292 292 #possibilities.sort() # Python sorts are case sensitive
293 293 self.AutoCompSetIgnoreCase(False)
294 294 self.AutoCompSetAutoHide(False)
295 295 #let compute the length ot text)last word
296 296 splitter = [' ', '(', '[', '{']
297 297 last_word = self.get_current_edit_buffer()
298 298 for breaker in splitter:
299 299 last_word = last_word.split(breaker)[-1]
300 300 self.AutoCompSetMaxHeight(len(possibilities))
301 301 self.AutoCompShow(len(last_word), " ".join(possibilities))
302 302
303 303
304 304 def scroll_to_bottom(self):
305 305 maxrange = self.GetScrollRange(wx.VERTICAL)
306 306 self.ScrollLines(maxrange)
307 307
308 308
309 309 def _on_enter(self):
310 310 """ Called when the return key is hit.
311 311 """
312 312 pass
313 313
314 314
315 315 def _on_key_down(self, event, skip=True):
316 316 """ Key press callback used for correcting behavior for
317 317 console-like interfaces: the cursor is constraint to be after
318 318 the last prompt.
319 319
320 320 Return True if event as been catched.
321 321 """
322 322 catched = True
323 323 # Intercept some specific keys.
324 324 if event.KeyCode == ord('L') and event.ControlDown() :
325 325 self.scroll_to_bottom()
326 326 elif event.KeyCode == ord('K') and event.ControlDown() :
327 327 self.replace_current_edit_buffer('')
328 328 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
329 329 self.ScrollPages(-1)
330 330 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
331 331 self.ScrollPages(1)
332 332 else:
333 333 catched = False
334 334
335 335 if self.AutoCompActive():
336 336 event.Skip()
337 337 else:
338 338 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
339 339 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
340 340 catched = True
341 341 self.write('\n')
342 342 self._on_enter()
343 343
344 344 elif event.KeyCode == wx.WXK_HOME:
345 345 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
346 346 self.GotoPos(self.current_prompt_pos)
347 347 catched = True
348 348
349 349 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
350 350 # FIXME: This behavior is not ideal: if the selection
351 351 # is already started, it will jump.
352 352 self.SetSelectionStart(self.current_prompt_pos)
353 353 self.SetSelectionEnd(self.GetCurrentPos())
354 354 catched = True
355 355
356 356 elif event.KeyCode == wx.WXK_UP:
357 357 if self.GetCurrentLine() > self.current_prompt_line:
358 358 if self.GetCurrentLine() == self.current_prompt_line + 1 \
359 359 and self.GetColumn(self.GetCurrentPos()) < \
360 360 self.GetColumn(self.current_prompt_pos):
361 361 self.GotoPos(self.current_prompt_pos)
362 362 else:
363 363 event.Skip()
364 364 catched = True
365 365
366 366 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
367 367 if self.GetCurrentPos() > self.current_prompt_pos:
368 368 event.Skip()
369 369 catched = True
370 370
371 371 if skip and not catched:
372 372 event.Skip()
373 373
374 374 return catched
375 375
376 376
377 377 def _on_key_up(self, event, skip=True):
378 378 """ If cursor is outside the editing region, put it back.
379 379 """
380 380 event.Skip()
381 381 if self.GetCurrentPos() < self.current_prompt_pos:
382 382 self.GotoPos(self.current_prompt_pos)
383 383
384 384
385 385
386 386
387 387 if __name__ == '__main__':
388 388 # Some simple code to test the console widget.
389 389 class MainWindow(wx.Frame):
390 390 def __init__(self, parent, id, title):
391 391 wx.Frame.__init__(self, parent, id, title, size=(300,250))
392 392 self._sizer = wx.BoxSizer(wx.VERTICAL)
393 393 self.console_widget = ConsoleWidget(self)
394 394 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
395 395 self.SetSizer(self._sizer)
396 396 self.SetAutoLayout(1)
397 397 self.Show(True)
398 398
399 399 app = wx.PySimpleApp()
400 400 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
401 401 w.SetSize((780, 460))
402 402 w.Show()
403 403
404 404 app.MainLoop()
405 405
406 406
@@ -1,159 +1,178 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 """
9 9
10 10 __docformat__ = "restructuredtext en"
11 11
12 12 #-------------------------------------------------------------------------------
13 13 # Copyright (C) 2008 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-------------------------------------------------------------------------------
18 18
19 19 #-------------------------------------------------------------------------------
20 20 # Imports
21 21 #-------------------------------------------------------------------------------
22 22
23 23
24 24 import wx
25 25 import re
26 from wx import stc
26 27 from console_widget import ConsoleWidget
27 28
28 29 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
29 30
31 #_COMMAND_BG = '#FAFAF1' # Nice green
32 _RUNNING_BUFFER_BG = '#FDFFBE' # Nice yellow
33
34 _RUNNING_BUFFER_MARKER = 31
35
36
30 37 #-------------------------------------------------------------------------------
31 38 # Classes to implement the Wx frontend
32 39 #-------------------------------------------------------------------------------
33 40 class IPythonWxController(PrefilterFrontEnd, ConsoleWidget):
34 41
35 42 output_prompt = \
36 43 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
37 44
38 45 #--------------------------------------------------------------------------
39 46 # Public API
40 47 #--------------------------------------------------------------------------
41 48
42 49 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
43 50 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
44 51 *args, **kwds):
45 52 """ Create Shell instance.
46 53 """
47 54 ConsoleWidget.__init__(self, parent, id, pos, size, style)
48 55 PrefilterFrontEnd.__init__(self)
49 56
50 57 # Capture Character keys
51 58 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
52 59
60 # Marker for running buffer.
61 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
62 background=_RUNNING_BUFFER_BG)
63
64
53 65
54 66 def do_completion(self, mode=None):
55 67 """ Do code completion.
56 68 mode can be 'text', 'popup' or 'none' to use default.
57 69 """
58 70 line = self.get_current_edit_buffer()
59 71 completions = self.complete(line)
60 self.write_completion(completions, mode=mode)
72 if len(completions)>0:
73 self.write_completion(completions, mode=mode)
61 74
62 75
63 76 def update_completion(self):
64 77 line = self.get_current_edit_buffer()
65 78 if self.AutoCompActive() and not line[-1] == '.':
66 79 line = line[:-1]
67 80 completions = self.complete(line)
68 81 choose_single = self.AutoCompGetChooseSingle()
69 82 self.AutoCompSetChooseSingle(False)
70 83 self.write_completion(completions, mode='popup')
71 84 self.AutoCompSetChooseSingle(choose_single)
72 85
73 86
74 def execute(self, *args, **kwargs):
87 def execute(self, python_string, raw_string=None):
75 88 self._cursor = wx.BusyCursor()
76 PrefilterFrontEnd.execute(self, *args, **kwargs)
89 if raw_string is None:
90 raw_string = python_string
91 end_line = self.current_prompt_line \
92 + max(1, len(raw_string.split('\n'))-1)
93 for i in range(self.current_prompt_line, end_line):
94 self.MarkerAdd(i, 31)
95 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
96
77 97
78
79 98 def after_execute(self):
80 99 PrefilterFrontEnd.after_execute(self)
81 100 if hasattr(self, '_cursor'):
82 101 del self._cursor
83 102
84 103 #--------------------------------------------------------------------------
85 104 # Private API
86 105 #--------------------------------------------------------------------------
87 106
88 107
89 108 def _on_key_down(self, event, skip=True):
90 109 """ Capture the character events, let the parent
91 110 widget handle them, and put our logic afterward.
92 111 """
93 112 current_line_number = self.GetCurrentLine()
94 113 if self.AutoCompActive():
95 114 event.Skip()
96 115 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
97 116 wx.CallAfter(self.do_completion)
98 117 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
99 118 wx.WXK_RIGHT):
100 119 wx.CallAfter(self.update_completion)
101 120 else:
102 121 # Up history
103 122 if event.KeyCode == wx.WXK_UP and (
104 123 ( current_line_number == self.current_prompt_line and
105 124 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
106 125 or event.ControlDown() ):
107 126 new_buffer = self.get_history_previous(
108 127 self.get_current_edit_buffer())
109 128 if new_buffer is not None:
110 129 self.replace_current_edit_buffer(new_buffer)
111 130 if self.GetCurrentLine() > self.current_prompt_line:
112 131 # Go to first line, for seemless history up.
113 132 self.GotoPos(self.current_prompt_pos)
114 133 # Down history
115 134 elif event.KeyCode == wx.WXK_DOWN and (
116 135 ( current_line_number == self.LineCount -1 and
117 136 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
118 137 or event.ControlDown() ):
119 138 new_buffer = self.get_history_next()
120 139 if new_buffer is not None:
121 140 self.replace_current_edit_buffer(new_buffer)
122 141 elif event.KeyCode == ord('\t'):
123 142 last_line = self.get_current_edit_buffer().split('\n')[-1]
124 143 if not re.match(r'^\s*$', last_line):
125 144 self.do_completion(mode='text')
126 145 else:
127 146 event.Skip()
128 147 else:
129 148 ConsoleWidget._on_key_down(self, event, skip=skip)
130 149
131 150
132 151 def _on_key_up(self, event, skip=True):
133 152 if event.KeyCode == 59:
134 153 # Intercepting '.'
135 154 event.Skip()
136 self.do_completion(mode='popup')
155 #self.do_completion(mode='popup')
137 156 else:
138 157 ConsoleWidget._on_key_up(self, event, skip=skip)
139 158
140 159
141 160 if __name__ == '__main__':
142 161 class MainWindow(wx.Frame):
143 162 def __init__(self, parent, id, title):
144 163 wx.Frame.__init__(self, parent, id, title, size=(300,250))
145 164 self._sizer = wx.BoxSizer(wx.VERTICAL)
146 165 self.shell = IPythonWxController(self)
147 166 self._sizer.Add(self.shell, 1, wx.EXPAND)
148 167 self.SetSizer(self._sizer)
149 168 self.SetAutoLayout(1)
150 169 self.Show(True)
151 170
152 171 app = wx.PySimpleApp()
153 172 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
154 173 frame.shell.SetFocus()
155 174 frame.SetSize((660, 460))
156 175 self = frame.shell
157 176
158 177 app.MainLoop()
159 178
General Comments 0
You need to be logged in to leave comments. Login now