##// END OF EJS Templates
Added a sleep as suggested by gael, so will not eat all cpu on raw_input ...
laurent.dufrechou@gmail.com -
Show More
@@ -1,937 +1,940 b''
1 #!/usr/bin/python
1 #!/usr/bin/python
2 # -*- coding: iso-8859-15 -*-
2 # -*- coding: iso-8859-15 -*-
3 '''
3 '''
4 Provides IPython WX console widgets.
4 Provides IPython WX console widgets.
5
5
6 @author: Laurent Dufrechou
6 @author: Laurent Dufrechou
7 laurent.dufrechou _at_ gmail.com
7 laurent.dufrechou _at_ gmail.com
8 This WX widget is based on the original work of Eitan Isaacson
8 This WX widget is based on the original work of Eitan Isaacson
9 that provided the console for the GTK toolkit.
9 that provided the console for the GTK toolkit.
10
10
11 Original work from:
11 Original work from:
12 @author: Eitan Isaacson
12 @author: Eitan Isaacson
13 @organization: IBM Corporation
13 @organization: IBM Corporation
14 @copyright: Copyright (c) 2007 IBM Corporation
14 @copyright: Copyright (c) 2007 IBM Corporation
15 @license: BSD
15 @license: BSD
16
16
17 All rights reserved. This program and the accompanying materials are made
17 All rights reserved. This program and the accompanying materials are made
18 available under the terms of the BSD which accompanies this distribution, and
18 available under the terms of the BSD which accompanies this distribution, and
19 is available at U{http://www.opensource.org/licenses/bsd-license.php}
19 is available at U{http://www.opensource.org/licenses/bsd-license.php}
20 '''
20 '''
21
21
22 __version__ = 0.9
22 __version__ = 0.9
23 __author__ = "Laurent Dufrechou"
23 __author__ = "Laurent Dufrechou"
24 __email__ = "laurent.dufrechou _at_ gmail.com"
24 __email__ = "laurent.dufrechou _at_ gmail.com"
25 __license__ = "BSD"
25 __license__ = "BSD"
26
26
27 import wx
27 import wx
28 import wx.stc as stc
28 import wx.stc as stc
29
29
30 import re
30 import re
31 from StringIO import StringIO
31 from StringIO import StringIO
32
32
33 import sys
33 import sys
34 import codecs
34 import codecs
35 import locale
35 import locale
36 import time
37
36 for enc in (locale.getpreferredencoding(),
38 for enc in (locale.getpreferredencoding(),
37 sys.getfilesystemencoding(),
39 sys.getfilesystemencoding(),
38 sys.getdefaultencoding()):
40 sys.getdefaultencoding()):
39 try:
41 try:
40 codecs.lookup(enc)
42 codecs.lookup(enc)
41 ENCODING = enc
43 ENCODING = enc
42 break
44 break
43 except LookupError:
45 except LookupError:
44 pass
46 pass
45 else:
47 else:
46 ENCODING = 'utf-8'
48 ENCODING = 'utf-8'
47
49
48 from ipshell_nonblocking import NonBlockingIPShell
50 from ipshell_nonblocking import NonBlockingIPShell
49
51
50 class WxNonBlockingIPShell(NonBlockingIPShell):
52 class WxNonBlockingIPShell(NonBlockingIPShell):
51 '''
53 '''
52 An NonBlockingIPShell Thread that is WX dependent.
54 An NonBlockingIPShell Thread that is WX dependent.
53 '''
55 '''
54 def __init__(self, parent,
56 def __init__(self, parent,
55 argv=[],user_ns={},user_global_ns=None,
57 argv=[],user_ns={},user_global_ns=None,
56 cin=None, cout=None, cerr=None,
58 cin=None, cout=None, cerr=None,
57 ask_exit_handler=None):
59 ask_exit_handler=None):
58
60
59 NonBlockingIPShell.__init__(self, argv, user_ns, user_global_ns,
61 NonBlockingIPShell.__init__(self, argv, user_ns, user_global_ns,
60 cin, cout, cerr,
62 cin, cout, cerr,
61 ask_exit_handler)
63 ask_exit_handler)
62
64
63 self.parent = parent
65 self.parent = parent
64
66
65 self.ask_exit_callback = ask_exit_handler
67 self.ask_exit_callback = ask_exit_handler
66 self._IP.exit = self._ask_exit
68 self._IP.exit = self._ask_exit
67
69
68 def addGUIShortcut(self, text, func):
70 def addGUIShortcut(self, text, func):
69 wx.CallAfter(self.parent.add_button_handler,
71 wx.CallAfter(self.parent.add_button_handler,
70 button_info={ 'text':text,
72 button_info={ 'text':text,
71 'func':self.parent.doExecuteLine(func)})
73 'func':self.parent.doExecuteLine(func)})
72
74
73 def _raw_input(self, prompt=''):
75 def _raw_input(self, prompt=''):
74 """ A replacement from python's raw_input.
76 """ A replacement from python's raw_input.
75 """
77 """
76 self.answer = None
78 self.answer = None
77 wx.CallAfter(self._yesNoBox, prompt)
79 wx.CallAfter(self._yesNoBox, prompt)
78 while self.answer is None:
80 while self.answer is None:
79 wx.Yield()
81 wx.Yield()
82 time.sleep(.1)
80 return self.answer
83 return self.answer
81
84
82 def _yesNoBox(self, prompt):
85 def _yesNoBox(self, prompt):
83 """ yes/no box managed with wx.CallAfter jsut in case caler is executed in a thread"""
86 """ yes/no box managed with wx.CallAfter jsut in case caler is executed in a thread"""
84 dlg = wx.TextEntryDialog(
87 dlg = wx.TextEntryDialog(
85 self.parent, prompt,
88 self.parent, prompt,
86 'Input requested', 'Python')
89 'Input requested', 'Python')
87 dlg.SetValue("")
90 dlg.SetValue("")
88
91
89 answer = ''
92 answer = ''
90 if dlg.ShowModal() == wx.ID_OK:
93 if dlg.ShowModal() == wx.ID_OK:
91 answer = dlg.GetValue()
94 answer = dlg.GetValue()
92
95
93 dlg.Destroy()
96 dlg.Destroy()
94 self.answer = answer
97 self.answer = answer
95
98
96 def _ask_exit(self):
99 def _ask_exit(self):
97 wx.CallAfter(self.ask_exit_callback, ())
100 wx.CallAfter(self.ask_exit_callback, ())
98
101
99 def _after_execute(self):
102 def _after_execute(self):
100 wx.CallAfter(self.parent.evtStateExecuteDone, ())
103 wx.CallAfter(self.parent.evtStateExecuteDone, ())
101
104
102
105
103 class WxConsoleView(stc.StyledTextCtrl):
106 class WxConsoleView(stc.StyledTextCtrl):
104 '''
107 '''
105 Specialized styled text control view for console-like workflow.
108 Specialized styled text control view for console-like workflow.
106 We use here a scintilla frontend thus it can be reused in any GUI that
109 We use here a scintilla frontend thus it can be reused in any GUI that
107 supports scintilla with less work.
110 supports scintilla with less work.
108
111
109 @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names.
112 @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names.
110 (with Black background)
113 (with Black background)
111 @type ANSI_COLORS_BLACK: dictionary
114 @type ANSI_COLORS_BLACK: dictionary
112
115
113 @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names.
116 @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names.
114 (with White background)
117 (with White background)
115 @type ANSI_COLORS_WHITE: dictionary
118 @type ANSI_COLORS_WHITE: dictionary
116
119
117 @ivar color_pat: Regex of terminal color pattern
120 @ivar color_pat: Regex of terminal color pattern
118 @type color_pat: _sre.SRE_Pattern
121 @type color_pat: _sre.SRE_Pattern
119 '''
122 '''
120 ANSI_STYLES_BLACK = {'0;30': [0, 'WHITE'], '0;31': [1, 'RED'],
123 ANSI_STYLES_BLACK = {'0;30': [0, 'WHITE'], '0;31': [1, 'RED'],
121 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
124 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
122 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
125 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
123 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
126 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
124 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
127 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
125 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
128 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
126 '1;34': [12, 'LIGHT BLUE'], '1;35':
129 '1;34': [12, 'LIGHT BLUE'], '1;35':
127 [13, 'MEDIUM VIOLET RED'],
130 [13, 'MEDIUM VIOLET RED'],
128 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
131 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
129
132
130 ANSI_STYLES_WHITE = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
133 ANSI_STYLES_WHITE = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
131 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
134 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
132 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
135 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
133 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
136 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
134 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
137 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
135 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
138 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
136 '1;34': [12, 'LIGHT BLUE'], '1;35':
139 '1;34': [12, 'LIGHT BLUE'], '1;35':
137 [13, 'MEDIUM VIOLET RED'],
140 [13, 'MEDIUM VIOLET RED'],
138 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
141 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
139
142
140 def __init__(self, parent, prompt, intro="", background_color="BLACK",
143 def __init__(self, parent, prompt, intro="", background_color="BLACK",
141 pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize,
144 pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize,
142 style=0, autocomplete_mode = 'IPYTHON'):
145 style=0, autocomplete_mode = 'IPYTHON'):
143 '''
146 '''
144 Initialize console view.
147 Initialize console view.
145
148
146 @param parent: Parent widget
149 @param parent: Parent widget
147 @param prompt: User specified prompt
150 @param prompt: User specified prompt
148 @type intro: string
151 @type intro: string
149 @param intro: User specified startup introduction string
152 @param intro: User specified startup introduction string
150 @type intro: string
153 @type intro: string
151 @param background_color: Can be BLACK or WHITE
154 @param background_color: Can be BLACK or WHITE
152 @type background_color: string
155 @type background_color: string
153 @param other: init param of styledTextControl (can be used as-is)
156 @param other: init param of styledTextControl (can be used as-is)
154 @param autocomplete_mode: Can be 'IPYTHON' or 'STC'
157 @param autocomplete_mode: Can be 'IPYTHON' or 'STC'
155 'IPYTHON' show autocompletion the ipython way
158 'IPYTHON' show autocompletion the ipython way
156 'STC" show it scintilla text control way
159 'STC" show it scintilla text control way
157 '''
160 '''
158 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
161 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
159
162
160 ####### Scintilla configuration ###################################
163 ####### Scintilla configuration ###################################
161
164
162 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside
165 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside
163 # the widget
166 # the widget
164 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
167 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
165 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
168 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
166
169
167 #We draw a line at position 80
170 #We draw a line at position 80
168 self.SetEdgeMode(stc.STC_EDGE_LINE)
171 self.SetEdgeMode(stc.STC_EDGE_LINE)
169 self.SetEdgeColumn(80)
172 self.SetEdgeColumn(80)
170 self.SetEdgeColour(wx.LIGHT_GREY)
173 self.SetEdgeColour(wx.LIGHT_GREY)
171
174
172 #self.SetViewWhiteSpace(True)
175 #self.SetViewWhiteSpace(True)
173 #self.SetViewEOL(True)
176 #self.SetViewEOL(True)
174 self.SetEOLMode(stc.STC_EOL_CRLF)
177 self.SetEOLMode(stc.STC_EOL_CRLF)
175 #self.SetWrapMode(stc.STC_WRAP_CHAR)
178 #self.SetWrapMode(stc.STC_WRAP_CHAR)
176 #self.SetWrapMode(stc.STC_WRAP_WORD)
179 #self.SetWrapMode(stc.STC_WRAP_WORD)
177 self.SetBufferedDraw(True)
180 self.SetBufferedDraw(True)
178 #self.SetUseAntiAliasing(True)
181 #self.SetUseAntiAliasing(True)
179 self.SetLayoutCache(stc.STC_CACHE_PAGE)
182 self.SetLayoutCache(stc.STC_CACHE_PAGE)
180 self.SetUndoCollection(False)
183 self.SetUndoCollection(False)
181 self.SetUseTabs(True)
184 self.SetUseTabs(True)
182 self.SetIndent(4)
185 self.SetIndent(4)
183 self.SetTabWidth(4)
186 self.SetTabWidth(4)
184
187
185 self.EnsureCaretVisible()
188 self.EnsureCaretVisible()
186
189
187 self.SetMargins(3, 3) #text is moved away from border with 3px
190 self.SetMargins(3, 3) #text is moved away from border with 3px
188 # Suppressing Scintilla margins
191 # Suppressing Scintilla margins
189 self.SetMarginWidth(0, 0)
192 self.SetMarginWidth(0, 0)
190 self.SetMarginWidth(1, 0)
193 self.SetMarginWidth(1, 0)
191 self.SetMarginWidth(2, 0)
194 self.SetMarginWidth(2, 0)
192
195
193 self.background_color = background_color
196 self.background_color = background_color
194 self.buildStyles()
197 self.buildStyles()
195
198
196 self.indent = 0
199 self.indent = 0
197 self.prompt_count = 0
200 self.prompt_count = 0
198 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
201 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
199
202
200 self.write(intro)
203 self.write(intro)
201 self.setPrompt(prompt)
204 self.setPrompt(prompt)
202 self.showPrompt()
205 self.showPrompt()
203
206
204 self.autocomplete_mode = autocomplete_mode
207 self.autocomplete_mode = autocomplete_mode
205
208
206 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
209 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
207
210
208 def buildStyles(self):
211 def buildStyles(self):
209 #we define platform specific fonts
212 #we define platform specific fonts
210 if wx.Platform == '__WXMSW__':
213 if wx.Platform == '__WXMSW__':
211 faces = { 'times': 'Times New Roman',
214 faces = { 'times': 'Times New Roman',
212 'mono' : 'Courier New',
215 'mono' : 'Courier New',
213 'helv' : 'Arial',
216 'helv' : 'Arial',
214 'other': 'Comic Sans MS',
217 'other': 'Comic Sans MS',
215 'size' : 10,
218 'size' : 10,
216 'size2': 8,
219 'size2': 8,
217 }
220 }
218 elif wx.Platform == '__WXMAC__':
221 elif wx.Platform == '__WXMAC__':
219 faces = { 'times': 'Times New Roman',
222 faces = { 'times': 'Times New Roman',
220 'mono' : 'Monaco',
223 'mono' : 'Monaco',
221 'helv' : 'Arial',
224 'helv' : 'Arial',
222 'other': 'Comic Sans MS',
225 'other': 'Comic Sans MS',
223 'size' : 10,
226 'size' : 10,
224 'size2': 8,
227 'size2': 8,
225 }
228 }
226 else:
229 else:
227 faces = { 'times': 'Times',
230 faces = { 'times': 'Times',
228 'mono' : 'Courier',
231 'mono' : 'Courier',
229 'helv' : 'Helvetica',
232 'helv' : 'Helvetica',
230 'other': 'new century schoolbook',
233 'other': 'new century schoolbook',
231 'size' : 10,
234 'size' : 10,
232 'size2': 8,
235 'size2': 8,
233 }
236 }
234
237
235 # make some styles
238 # make some styles
236 if self.background_color != "BLACK":
239 if self.background_color != "BLACK":
237 self.background_color = "WHITE"
240 self.background_color = "WHITE"
238 self.SetCaretForeground("BLACK")
241 self.SetCaretForeground("BLACK")
239 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
242 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
240 else:
243 else:
241 self.SetCaretForeground("WHITE")
244 self.SetCaretForeground("WHITE")
242 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
245 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
243
246
244 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
247 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
245 "fore:%s,back:%s,size:%d,face:%s"
248 "fore:%s,back:%s,size:%d,face:%s"
246 % (self.ANSI_STYLES['0;30'][1],
249 % (self.ANSI_STYLES['0;30'][1],
247 self.background_color,
250 self.background_color,
248 faces['size'], faces['mono']))
251 faces['size'], faces['mono']))
249 self.StyleClearAll()
252 self.StyleClearAll()
250 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
253 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
251 "fore:#FF0000,back:#0000FF,bold")
254 "fore:#FF0000,back:#0000FF,bold")
252 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
255 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
253 "fore:#000000,back:#FF0000,bold")
256 "fore:#000000,back:#FF0000,bold")
254
257
255 for style in self.ANSI_STYLES.values():
258 for style in self.ANSI_STYLES.values():
256 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
259 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
257
260
258 #######################################################################
261 #######################################################################
259
262
260 def setBackgroundColor(self, color):
263 def setBackgroundColor(self, color):
261 self.background_color = color
264 self.background_color = color
262 self.buildStyles()
265 self.buildStyles()
263
266
264 def getBackgroundColor(self, color):
267 def getBackgroundColor(self, color):
265 return self.background_color
268 return self.background_color
266
269
267 def asyncWrite(self, text):
270 def asyncWrite(self, text):
268 '''
271 '''
269 Write given text to buffer in an asynchroneous way.
272 Write given text to buffer in an asynchroneous way.
270 It is used from another thread to be able to acces the GUI.
273 It is used from another thread to be able to acces the GUI.
271 @param text: Text to append
274 @param text: Text to append
272 @type text: string
275 @type text: string
273 '''
276 '''
274 try:
277 try:
275 wx.MutexGuiEnter()
278 wx.MutexGuiEnter()
276
279
277 #be sure not to be interrutpted before the MutexGuiLeave!
280 #be sure not to be interrutpted before the MutexGuiLeave!
278 self.write(text)
281 self.write(text)
279
282
280 except KeyboardInterrupt:
283 except KeyboardInterrupt:
281 wx.MutexGuiLeave()
284 wx.MutexGuiLeave()
282 raise KeyboardInterrupt
285 raise KeyboardInterrupt
283 wx.MutexGuiLeave()
286 wx.MutexGuiLeave()
284
287
285
288
286 def write(self, text):
289 def write(self, text):
287 '''
290 '''
288 Write given text to buffer.
291 Write given text to buffer.
289
292
290 @param text: Text to append.
293 @param text: Text to append.
291 @type text: string
294 @type text: string
292 '''
295 '''
293 segments = self.color_pat.split(text)
296 segments = self.color_pat.split(text)
294 segment = segments.pop(0)
297 segment = segments.pop(0)
295 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
298 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
296 self.AppendText(segment)
299 self.AppendText(segment)
297
300
298 if segments:
301 if segments:
299 ansi_tags = self.color_pat.findall(text)
302 ansi_tags = self.color_pat.findall(text)
300
303
301 for tag in ansi_tags:
304 for tag in ansi_tags:
302 i = segments.index(tag)
305 i = segments.index(tag)
303 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
306 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
304 self.AppendText(segments[i+1])
307 self.AppendText(segments[i+1])
305
308
306 if tag != '0':
309 if tag != '0':
307 self.SetStyling(len(segments[i+1]), self.ANSI_STYLES[tag][0])
310 self.SetStyling(len(segments[i+1]), self.ANSI_STYLES[tag][0])
308
311
309 segments.pop(i)
312 segments.pop(i)
310
313
311 self.moveCursor(self.getCurrentLineEnd())
314 self.moveCursor(self.getCurrentLineEnd())
312
315
313 def getPromptLen(self):
316 def getPromptLen(self):
314 '''
317 '''
315 Return the length of current prompt
318 Return the length of current prompt
316 '''
319 '''
317 return len(str(self.prompt_count)) + 7
320 return len(str(self.prompt_count)) + 7
318
321
319 def setPrompt(self, prompt):
322 def setPrompt(self, prompt):
320 self.prompt = prompt
323 self.prompt = prompt
321
324
322 def setIndentation(self, indentation):
325 def setIndentation(self, indentation):
323 self.indent = indentation
326 self.indent = indentation
324
327
325 def setPromptCount(self, count):
328 def setPromptCount(self, count):
326 self.prompt_count = count
329 self.prompt_count = count
327
330
328 def showPrompt(self):
331 def showPrompt(self):
329 '''
332 '''
330 Prints prompt at start of line.
333 Prints prompt at start of line.
331
334
332 @param prompt: Prompt to print.
335 @param prompt: Prompt to print.
333 @type prompt: string
336 @type prompt: string
334 '''
337 '''
335 self.write(self.prompt)
338 self.write(self.prompt)
336 #now we update the position of end of prompt
339 #now we update the position of end of prompt
337 self.current_start = self.getCurrentLineEnd()
340 self.current_start = self.getCurrentLineEnd()
338
341
339 autoindent = self.indent*' '
342 autoindent = self.indent*' '
340 autoindent = autoindent.replace(' ','\t')
343 autoindent = autoindent.replace(' ','\t')
341 self.write(autoindent)
344 self.write(autoindent)
342
345
343 def changeLine(self, text):
346 def changeLine(self, text):
344 '''
347 '''
345 Replace currently entered command line with given text.
348 Replace currently entered command line with given text.
346
349
347 @param text: Text to use as replacement.
350 @param text: Text to use as replacement.
348 @type text: string
351 @type text: string
349 '''
352 '''
350 self.SetSelection(self.getCurrentPromptStart(), self.getCurrentLineEnd())
353 self.SetSelection(self.getCurrentPromptStart(), self.getCurrentLineEnd())
351 self.ReplaceSelection(text)
354 self.ReplaceSelection(text)
352 self.moveCursor(self.getCurrentLineEnd())
355 self.moveCursor(self.getCurrentLineEnd())
353
356
354 def getCurrentPromptStart(self):
357 def getCurrentPromptStart(self):
355 return self.current_start
358 return self.current_start
356
359
357 def getCurrentLineStart(self):
360 def getCurrentLineStart(self):
358 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
361 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
359
362
360 def getCurrentLineEnd(self):
363 def getCurrentLineEnd(self):
361 return self.GetLength()
364 return self.GetLength()
362
365
363 def getCurrentLine(self):
366 def getCurrentLine(self):
364 '''
367 '''
365 Get text in current command line.
368 Get text in current command line.
366
369
367 @return: Text of current command line.
370 @return: Text of current command line.
368 @rtype: string
371 @rtype: string
369 '''
372 '''
370 return self.GetTextRange(self.getCurrentPromptStart(),
373 return self.GetTextRange(self.getCurrentPromptStart(),
371 self.getCurrentLineEnd())
374 self.getCurrentLineEnd())
372
375
373 def moveCursorOnNewValidKey(self):
376 def moveCursorOnNewValidKey(self):
374 #If cursor is at wrong position put it at last line...
377 #If cursor is at wrong position put it at last line...
375 if self.GetCurrentPos() < self.getCurrentPromptStart():
378 if self.GetCurrentPos() < self.getCurrentPromptStart():
376 self.GotoPos(self.getCurrentPromptStart())
379 self.GotoPos(self.getCurrentPromptStart())
377
380
378 def removeFromTo(self, from_pos, to_pos):
381 def removeFromTo(self, from_pos, to_pos):
379 if from_pos < to_pos:
382 if from_pos < to_pos:
380 self.SetSelection(from_pos, to_pos)
383 self.SetSelection(from_pos, to_pos)
381 self.DeleteBack()
384 self.DeleteBack()
382
385
383 def removeCurrentLine(self):
386 def removeCurrentLine(self):
384 self.LineDelete()
387 self.LineDelete()
385
388
386 def moveCursor(self, position):
389 def moveCursor(self, position):
387 self.GotoPos(position)
390 self.GotoPos(position)
388
391
389 def getCursorPos(self):
392 def getCursorPos(self):
390 return self.GetCurrentPos()
393 return self.GetCurrentPos()
391
394
392 def selectFromTo(self, from_pos, to_pos):
395 def selectFromTo(self, from_pos, to_pos):
393 self.SetSelectionStart(from_pos)
396 self.SetSelectionStart(from_pos)
394 self.SetSelectionEnd(to_pos)
397 self.SetSelectionEnd(to_pos)
395
398
396 def writeHistory(self, history):
399 def writeHistory(self, history):
397 self.removeFromTo(self.getCurrentPromptStart(), self.getCurrentLineEnd())
400 self.removeFromTo(self.getCurrentPromptStart(), self.getCurrentLineEnd())
398 self.changeLine(history)
401 self.changeLine(history)
399
402
400 def setCompletionMethod(self, completion):
403 def setCompletionMethod(self, completion):
401 if completion in ['IPYTHON', 'STC']:
404 if completion in ['IPYTHON', 'STC']:
402 self.autocomplete_mode = completion
405 self.autocomplete_mode = completion
403 else:
406 else:
404 raise AttributeError
407 raise AttributeError
405
408
406 def getCompletionMethod(self, completion):
409 def getCompletionMethod(self, completion):
407 return self.autocomplete_mode
410 return self.autocomplete_mode
408
411
409 def writeCompletion(self, possibilities):
412 def writeCompletion(self, possibilities):
410 if self.autocomplete_mode == 'IPYTHON':
413 if self.autocomplete_mode == 'IPYTHON':
411 max_len = len(max(possibilities, key=len))
414 max_len = len(max(possibilities, key=len))
412 max_symbol = ' '*max_len
415 max_symbol = ' '*max_len
413
416
414 #now we check how much symbol we can put on a line...
417 #now we check how much symbol we can put on a line...
415 test_buffer = max_symbol + ' '*4
418 test_buffer = max_symbol + ' '*4
416
419
417 allowed_symbols = 80/len(test_buffer)
420 allowed_symbols = 80/len(test_buffer)
418 if allowed_symbols == 0:
421 if allowed_symbols == 0:
419 allowed_symbols = 1
422 allowed_symbols = 1
420
423
421 pos = 1
424 pos = 1
422 buf = ''
425 buf = ''
423 for symbol in possibilities:
426 for symbol in possibilities:
424 #buf += symbol+'\n'#*spaces)
427 #buf += symbol+'\n'#*spaces)
425 if pos < allowed_symbols:
428 if pos < allowed_symbols:
426 spaces = max_len - len(symbol) + 4
429 spaces = max_len - len(symbol) + 4
427 buf += symbol+' '*spaces
430 buf += symbol+' '*spaces
428 pos += 1
431 pos += 1
429 else:
432 else:
430 buf += symbol+'\n'
433 buf += symbol+'\n'
431 pos = 1
434 pos = 1
432 self.write(buf)
435 self.write(buf)
433 else:
436 else:
434 possibilities.sort() # Python sorts are case sensitive
437 possibilities.sort() # Python sorts are case sensitive
435 self.AutoCompSetIgnoreCase(False)
438 self.AutoCompSetIgnoreCase(False)
436 self.AutoCompSetAutoHide(False)
439 self.AutoCompSetAutoHide(False)
437 #let compute the length ot last word
440 #let compute the length ot last word
438 splitter = [' ', '(', '[', '{','=']
441 splitter = [' ', '(', '[', '{','=']
439 last_word = self.getCurrentLine()
442 last_word = self.getCurrentLine()
440 for breaker in splitter:
443 for breaker in splitter:
441 last_word = last_word.split(breaker)[-1]
444 last_word = last_word.split(breaker)[-1]
442 self.AutoCompShow(len(last_word), " ".join(possibilities))
445 self.AutoCompShow(len(last_word), " ".join(possibilities))
443
446
444 def _onKeypress(self, event, skip=True):
447 def _onKeypress(self, event, skip=True):
445 '''
448 '''
446 Key press callback used for correcting behavior for console-like
449 Key press callback used for correcting behavior for console-like
447 interfaces. For example 'home' should go to prompt, not to begining of
450 interfaces. For example 'home' should go to prompt, not to begining of
448 line.
451 line.
449
452
450 @param widget: Widget that key press accored in.
453 @param widget: Widget that key press accored in.
451 @type widget: gtk.Widget
454 @type widget: gtk.Widget
452 @param event: Event object
455 @param event: Event object
453 @type event: gtk.gdk.Event
456 @type event: gtk.gdk.Event
454
457
455 @return: Return True if event as been catched.
458 @return: Return True if event as been catched.
456 @rtype: boolean
459 @rtype: boolean
457 '''
460 '''
458 if not self.AutoCompActive():
461 if not self.AutoCompActive():
459 if event.GetKeyCode() == wx.WXK_HOME:
462 if event.GetKeyCode() == wx.WXK_HOME:
460 if event.Modifiers == wx.MOD_NONE:
463 if event.Modifiers == wx.MOD_NONE:
461 self.moveCursorOnNewValidKey()
464 self.moveCursorOnNewValidKey()
462 self.moveCursor(self.getCurrentPromptStart())
465 self.moveCursor(self.getCurrentPromptStart())
463 return True
466 return True
464 elif event.Modifiers == wx.MOD_SHIFT:
467 elif event.Modifiers == wx.MOD_SHIFT:
465 self.moveCursorOnNewValidKey()
468 self.moveCursorOnNewValidKey()
466 self.selectFromTo(self.getCurrentPromptStart(), self.getCursorPos())
469 self.selectFromTo(self.getCurrentPromptStart(), self.getCursorPos())
467 return True
470 return True
468 else:
471 else:
469 return False
472 return False
470
473
471 elif event.GetKeyCode() == wx.WXK_LEFT:
474 elif event.GetKeyCode() == wx.WXK_LEFT:
472 if event.Modifiers == wx.MOD_NONE:
475 if event.Modifiers == wx.MOD_NONE:
473 self.moveCursorOnNewValidKey()
476 self.moveCursorOnNewValidKey()
474
477
475 self.moveCursor(self.getCursorPos()-1)
478 self.moveCursor(self.getCursorPos()-1)
476 if self.getCursorPos() < self.getCurrentPromptStart():
479 if self.getCursorPos() < self.getCurrentPromptStart():
477 self.moveCursor(self.getCurrentPromptStart())
480 self.moveCursor(self.getCurrentPromptStart())
478 return True
481 return True
479
482
480 elif event.GetKeyCode() == wx.WXK_BACK:
483 elif event.GetKeyCode() == wx.WXK_BACK:
481 self.moveCursorOnNewValidKey()
484 self.moveCursorOnNewValidKey()
482 if self.getCursorPos() > self.getCurrentPromptStart():
485 if self.getCursorPos() > self.getCurrentPromptStart():
483 event.Skip()
486 event.Skip()
484 return True
487 return True
485
488
486 if skip:
489 if skip:
487 if event.GetKeyCode() not in [wx.WXK_PAGEUP, wx.WXK_PAGEDOWN]\
490 if event.GetKeyCode() not in [wx.WXK_PAGEUP, wx.WXK_PAGEDOWN]\
488 and event.Modifiers == wx.MOD_NONE:
491 and event.Modifiers == wx.MOD_NONE:
489 self.moveCursorOnNewValidKey()
492 self.moveCursorOnNewValidKey()
490
493
491 event.Skip()
494 event.Skip()
492 return True
495 return True
493 return False
496 return False
494 else:
497 else:
495 event.Skip()
498 event.Skip()
496
499
497 def OnUpdateUI(self, evt):
500 def OnUpdateUI(self, evt):
498 # check for matching braces
501 # check for matching braces
499 braceAtCaret = -1
502 braceAtCaret = -1
500 braceOpposite = -1
503 braceOpposite = -1
501 charBefore = None
504 charBefore = None
502 caretPos = self.GetCurrentPos()
505 caretPos = self.GetCurrentPos()
503
506
504 if caretPos > 0:
507 if caretPos > 0:
505 charBefore = self.GetCharAt(caretPos - 1)
508 charBefore = self.GetCharAt(caretPos - 1)
506 styleBefore = self.GetStyleAt(caretPos - 1)
509 styleBefore = self.GetStyleAt(caretPos - 1)
507
510
508 # check before
511 # check before
509 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
512 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
510 braceAtCaret = caretPos - 1
513 braceAtCaret = caretPos - 1
511
514
512 # check after
515 # check after
513 if braceAtCaret < 0:
516 if braceAtCaret < 0:
514 charAfter = self.GetCharAt(caretPos)
517 charAfter = self.GetCharAt(caretPos)
515 styleAfter = self.GetStyleAt(caretPos)
518 styleAfter = self.GetStyleAt(caretPos)
516
519
517 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
520 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
518 braceAtCaret = caretPos
521 braceAtCaret = caretPos
519
522
520 if braceAtCaret >= 0:
523 if braceAtCaret >= 0:
521 braceOpposite = self.BraceMatch(braceAtCaret)
524 braceOpposite = self.BraceMatch(braceAtCaret)
522
525
523 if braceAtCaret != -1 and braceOpposite == -1:
526 if braceAtCaret != -1 and braceOpposite == -1:
524 self.BraceBadLight(braceAtCaret)
527 self.BraceBadLight(braceAtCaret)
525 else:
528 else:
526 self.BraceHighlight(braceAtCaret, braceOpposite)
529 self.BraceHighlight(braceAtCaret, braceOpposite)
527 #pt = self.PointFromPosition(braceOpposite)
530 #pt = self.PointFromPosition(braceOpposite)
528 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
531 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
529 #print pt
532 #print pt
530 #self.Refresh(False)
533 #self.Refresh(False)
531
534
532 class IPShellWidget(wx.Panel):
535 class IPShellWidget(wx.Panel):
533 '''
536 '''
534 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
537 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
535 If you want to port this to any other GUI toolkit, just replace the
538 If you want to port this to any other GUI toolkit, just replace the
536 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
539 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
537 from whatever container you want. I've choosed to derivate from a wx.Panel
540 from whatever container you want. I've choosed to derivate from a wx.Panel
538 because it seems to be more useful
541 because it seems to be more useful
539 Any idea to make it more 'generic' welcomed.
542 Any idea to make it more 'generic' welcomed.
540 '''
543 '''
541
544
542 def __init__(self, parent, intro=None,
545 def __init__(self, parent, intro=None,
543 background_color="BLACK", add_button_handler=None,
546 background_color="BLACK", add_button_handler=None,
544 wx_ip_shell=None, user_ns={},user_global_ns=None,
547 wx_ip_shell=None, user_ns={},user_global_ns=None,
545 ):
548 ):
546 '''
549 '''
547 Initialize.
550 Initialize.
548 Instanciate an IPython thread.
551 Instanciate an IPython thread.
549 Instanciate a WxConsoleView.
552 Instanciate a WxConsoleView.
550 Redirect I/O to console.
553 Redirect I/O to console.
551 '''
554 '''
552 wx.Panel.__init__(self,parent,wx.ID_ANY)
555 wx.Panel.__init__(self,parent,wx.ID_ANY)
553
556
554 self.parent = parent
557 self.parent = parent
555 ### IPython non blocking shell instanciation ###
558 ### IPython non blocking shell instanciation ###
556 self.cout = StringIO()
559 self.cout = StringIO()
557 self.add_button_handler = add_button_handler
560 self.add_button_handler = add_button_handler
558
561
559 if wx_ip_shell is not None:
562 if wx_ip_shell is not None:
560 self.IP = wx_ip_shell
563 self.IP = wx_ip_shell
561 else:
564 else:
562 self.IP = WxNonBlockingIPShell(self,
565 self.IP = WxNonBlockingIPShell(self,
563 cout = self.cout, cerr = self.cout,
566 cout = self.cout, cerr = self.cout,
564 ask_exit_handler = self.askExitCallback)
567 ask_exit_handler = self.askExitCallback)
565
568
566 ### IPython wx console view instanciation ###
569 ### IPython wx console view instanciation ###
567 #If user didn't defined an intro text, we create one for him
570 #If user didn't defined an intro text, we create one for him
568 #If you really wnat an empty intro just call wxIPythonViewPanel
571 #If you really wnat an empty intro just call wxIPythonViewPanel
569 #with intro=''
572 #with intro=''
570 if intro is None:
573 if intro is None:
571 welcome_text = "Welcome to WxIPython Shell.\n\n"
574 welcome_text = "Welcome to WxIPython Shell.\n\n"
572 welcome_text+= self.IP.get_banner()
575 welcome_text+= self.IP.get_banner()
573 welcome_text+= "!command -> Execute command in shell\n"
576 welcome_text+= "!command -> Execute command in shell\n"
574 welcome_text+= "TAB -> Autocompletion\n"
577 welcome_text+= "TAB -> Autocompletion\n"
575 else:
578 else:
576 welcome_text = intro
579 welcome_text = intro
577
580
578 self.text_ctrl = WxConsoleView(self,
581 self.text_ctrl = WxConsoleView(self,
579 self.IP.get_prompt(),
582 self.IP.get_prompt(),
580 intro=welcome_text,
583 intro=welcome_text,
581 background_color=background_color)
584 background_color=background_color)
582
585
583 option_text = wx.StaticText(self, -1, "Options:")
586 option_text = wx.StaticText(self, -1, "Options:")
584 self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion")
587 self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion")
585 self.completion_option.SetToolTip(wx.ToolTip(
588 self.completion_option.SetToolTip(wx.ToolTip(
586 "Selects the completion type:\nEither Ipython default style or Scintilla one"))
589 "Selects the completion type:\nEither Ipython default style or Scintilla one"))
587 #self.completion_option.SetValue(False)
590 #self.completion_option.SetValue(False)
588 self.background_option = wx.CheckBox(self, -1, "White Background")
591 self.background_option = wx.CheckBox(self, -1, "White Background")
589 self.background_option.SetToolTip(wx.ToolTip(
592 self.background_option.SetToolTip(wx.ToolTip(
590 "Selects the back ground color: BLACK or WHITE"))
593 "Selects the back ground color: BLACK or WHITE"))
591 #self.background_option.SetValue(False)
594 #self.background_option.SetValue(False)
592 self.threading_option = wx.CheckBox(self, -1, "Execute in thread")
595 self.threading_option = wx.CheckBox(self, -1, "Execute in thread")
593 self.threading_option.SetToolTip(wx.ToolTip(
596 self.threading_option.SetToolTip(wx.ToolTip(
594 "Use threading: infinite loop don't freeze the GUI and commands can be breaked\nNo threading: maximum compatibility"))
597 "Use threading: infinite loop don't freeze the GUI and commands can be breaked\nNo threading: maximum compatibility"))
595 #self.threading_option.SetValue(False)
598 #self.threading_option.SetValue(False)
596
599
597 self.options={'completion':{'value':'IPYTHON',
600 self.options={'completion':{'value':'IPYTHON',
598 'checkbox':self.completion_option,'STC':True,'IPYTHON':False,
601 'checkbox':self.completion_option,'STC':True,'IPYTHON':False,
599 'setfunc':self.text_ctrl.setCompletionMethod},
602 'setfunc':self.text_ctrl.setCompletionMethod},
600 'background_color':{'value':'BLACK',
603 'background_color':{'value':'BLACK',
601 'checkbox':self.background_option,'WHITE':True,'BLACK':False,
604 'checkbox':self.background_option,'WHITE':True,'BLACK':False,
602 'setfunc':self.text_ctrl.setBackgroundColor},
605 'setfunc':self.text_ctrl.setBackgroundColor},
603 'threading':{'value':'True',
606 'threading':{'value':'True',
604 'checkbox':self.threading_option,'True':True,'False':False,
607 'checkbox':self.threading_option,'True':True,'False':False,
605 'setfunc':self.IP.set_threading},
608 'setfunc':self.IP.set_threading},
606 }
609 }
607
610
608 #self.cout.write dEfault option is asynchroneous because default sate is threading ON
611 #self.cout.write dEfault option is asynchroneous because default sate is threading ON
609 self.cout.write = self.text_ctrl.asyncWrite
612 self.cout.write = self.text_ctrl.asyncWrite
610 #we reloard options
613 #we reloard options
611 self.reloadOptions(self.options)
614 self.reloadOptions(self.options)
612
615
613 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress)
616 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress)
614 self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion)
617 self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion)
615 self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor)
618 self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor)
616 self.threading_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionThreading)
619 self.threading_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionThreading)
617
620
618 ### making the layout of the panel ###
621 ### making the layout of the panel ###
619 sizer = wx.BoxSizer(wx.VERTICAL)
622 sizer = wx.BoxSizer(wx.VERTICAL)
620 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
623 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
621 option_sizer = wx.BoxSizer(wx.HORIZONTAL)
624 option_sizer = wx.BoxSizer(wx.HORIZONTAL)
622 sizer.Add(option_sizer, 0)
625 sizer.Add(option_sizer, 0)
623 option_sizer.AddMany([(10, 20),
626 option_sizer.AddMany([(10, 20),
624 (option_text, 0, wx.ALIGN_CENTER_VERTICAL),
627 (option_text, 0, wx.ALIGN_CENTER_VERTICAL),
625 (5, 5),
628 (5, 5),
626 (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL),
629 (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL),
627 (8, 8),
630 (8, 8),
628 (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL),
631 (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL),
629 (8, 8),
632 (8, 8),
630 (self.threading_option, 0, wx.ALIGN_CENTER_VERTICAL)
633 (self.threading_option, 0, wx.ALIGN_CENTER_VERTICAL)
631 ])
634 ])
632 self.SetAutoLayout(True)
635 self.SetAutoLayout(True)
633 sizer.Fit(self)
636 sizer.Fit(self)
634 sizer.SetSizeHints(self)
637 sizer.SetSizeHints(self)
635 self.SetSizer(sizer)
638 self.SetSizer(sizer)
636 #and we focus on the widget :)
639 #and we focus on the widget :)
637 self.SetFocus()
640 self.SetFocus()
638
641
639 #widget state management (for key handling different cases)
642 #widget state management (for key handling different cases)
640 self.setCurrentState('IDLE')
643 self.setCurrentState('IDLE')
641 self.pager_state = 'DONE'
644 self.pager_state = 'DONE'
642 self.raw_input_current_line = 0
645 self.raw_input_current_line = 0
643
646
644 def askExitCallback(self, event):
647 def askExitCallback(self, event):
645 self.askExitHandler(event)
648 self.askExitHandler(event)
646
649
647 #---------------------- IPython Thread Management ------------------------
650 #---------------------- IPython Thread Management ------------------------
648 def stateDoExecuteLine(self):
651 def stateDoExecuteLine(self):
649 lines=self.text_ctrl.getCurrentLine()
652 lines=self.text_ctrl.getCurrentLine()
650 self.text_ctrl.write('\n')
653 self.text_ctrl.write('\n')
651 lines_to_execute = lines.replace('\t',' '*4)
654 lines_to_execute = lines.replace('\t',' '*4)
652 lines_to_execute = lines_to_execute.replace('\r','')
655 lines_to_execute = lines_to_execute.replace('\r','')
653 self.IP.do_execute(lines_to_execute.encode(ENCODING))
656 self.IP.do_execute(lines_to_execute.encode(ENCODING))
654 self.updateHistoryTracker(lines)
657 self.updateHistoryTracker(lines)
655 self.setCurrentState('WAIT_END_OF_EXECUTION')
658 self.setCurrentState('WAIT_END_OF_EXECUTION')
656
659
657 def evtStateExecuteDone(self,evt):
660 def evtStateExecuteDone(self,evt):
658 self.doc = self.IP.get_doc_text()
661 self.doc = self.IP.get_doc_text()
659 self.help = self.IP.get_help_text()
662 self.help = self.IP.get_help_text()
660 if self.doc:
663 if self.doc:
661 self.pager_lines = self.doc[7:].split('\n')
664 self.pager_lines = self.doc[7:].split('\n')
662 self.pager_state = 'INIT'
665 self.pager_state = 'INIT'
663 self.setCurrentState('SHOW_DOC')
666 self.setCurrentState('SHOW_DOC')
664 self.pager(self.doc)
667 self.pager(self.doc)
665 elif self.help:
668 elif self.help:
666 self.pager_lines = self.help.split('\n')
669 self.pager_lines = self.help.split('\n')
667 self.pager_state = 'INIT'
670 self.pager_state = 'INIT'
668 self.setCurrentState('SHOW_DOC')
671 self.setCurrentState('SHOW_DOC')
669 self.pager(self.help)
672 self.pager(self.help)
670 else:
673 else:
671 if(self.text_ctrl.getCursorPos()!=0):
674 if(self.text_ctrl.getCursorPos()!=0):
672 self.text_ctrl.removeCurrentLine()
675 self.text_ctrl.removeCurrentLine()
673 self.stateShowPrompt()
676 self.stateShowPrompt()
674
677
675 def stateShowPrompt(self):
678 def stateShowPrompt(self):
676 self.setCurrentState('SHOW_PROMPT')
679 self.setCurrentState('SHOW_PROMPT')
677 self.text_ctrl.setPrompt(self.IP.get_prompt())
680 self.text_ctrl.setPrompt(self.IP.get_prompt())
678 self.text_ctrl.setIndentation(self.IP.get_indentation())
681 self.text_ctrl.setIndentation(self.IP.get_indentation())
679 self.text_ctrl.setPromptCount(self.IP.get_prompt_count())
682 self.text_ctrl.setPromptCount(self.IP.get_prompt_count())
680 self.text_ctrl.showPrompt()
683 self.text_ctrl.showPrompt()
681 self.IP.init_history_index()
684 self.IP.init_history_index()
682 self.setCurrentState('IDLE')
685 self.setCurrentState('IDLE')
683
686
684 def setCurrentState(self, state):
687 def setCurrentState(self, state):
685 self.cur_state = state
688 self.cur_state = state
686 self.updateStatusTracker(self.cur_state)
689 self.updateStatusTracker(self.cur_state)
687
690
688 def pager(self,text):
691 def pager(self,text):
689
692
690 if self.pager_state == 'INIT':
693 if self.pager_state == 'INIT':
691 #print >>sys.__stdout__,"PAGER state:",self.pager_state
694 #print >>sys.__stdout__,"PAGER state:",self.pager_state
692 self.pager_nb_lines = len(self.pager_lines)
695 self.pager_nb_lines = len(self.pager_lines)
693 self.pager_index = 0
696 self.pager_index = 0
694 self.pager_do_remove = False
697 self.pager_do_remove = False
695 self.text_ctrl.write('\n')
698 self.text_ctrl.write('\n')
696 self.pager_state = 'PROCESS_LINES'
699 self.pager_state = 'PROCESS_LINES'
697
700
698 if self.pager_state == 'PROCESS_LINES':
701 if self.pager_state == 'PROCESS_LINES':
699 #print >>sys.__stdout__,"PAGER state:",self.pager_state
702 #print >>sys.__stdout__,"PAGER state:",self.pager_state
700 if self.pager_do_remove == True:
703 if self.pager_do_remove == True:
701 self.text_ctrl.removeCurrentLine()
704 self.text_ctrl.removeCurrentLine()
702 self.pager_do_remove = False
705 self.pager_do_remove = False
703
706
704 if self.pager_nb_lines > 10:
707 if self.pager_nb_lines > 10:
705 #print >>sys.__stdout__,"PAGER processing 10 lines"
708 #print >>sys.__stdout__,"PAGER processing 10 lines"
706 if self.pager_index > 0:
709 if self.pager_index > 0:
707 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
710 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
708 else:
711 else:
709 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
712 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
710
713
711 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
714 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
712 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
715 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
713 self.pager_index += 10
716 self.pager_index += 10
714 self.pager_nb_lines -= 10
717 self.pager_nb_lines -= 10
715 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
718 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
716 self.pager_do_remove = True
719 self.pager_do_remove = True
717 self.pager_state = 'WAITING'
720 self.pager_state = 'WAITING'
718 return
721 return
719 else:
722 else:
720 #print >>sys.__stdout__,"PAGER processing last lines"
723 #print >>sys.__stdout__,"PAGER processing last lines"
721 if self.pager_nb_lines > 0:
724 if self.pager_nb_lines > 0:
722 if self.pager_index > 0:
725 if self.pager_index > 0:
723 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
726 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
724 else:
727 else:
725 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
728 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
726
729
727 self.pager_index += 1
730 self.pager_index += 1
728 self.pager_nb_lines -= 1
731 self.pager_nb_lines -= 1
729 if self.pager_nb_lines > 0:
732 if self.pager_nb_lines > 0:
730 for line in self.pager_lines[self.pager_index:]:
733 for line in self.pager_lines[self.pager_index:]:
731 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
734 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
732 self.pager_nb_lines = 0
735 self.pager_nb_lines = 0
733 self.pager_state = 'DONE'
736 self.pager_state = 'DONE'
734 self.stateShowPrompt()
737 self.stateShowPrompt()
735
738
736 #------------------------ Key Handler ------------------------------------
739 #------------------------ Key Handler ------------------------------------
737 def keyPress(self, event):
740 def keyPress(self, event):
738 '''
741 '''
739 Key press callback with plenty of shell goodness, like history,
742 Key press callback with plenty of shell goodness, like history,
740 autocompletions, etc.
743 autocompletions, etc.
741 '''
744 '''
742 if event.GetKeyCode() == ord('C'):
745 if event.GetKeyCode() == ord('C'):
743 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
746 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
744 if self.cur_state == 'WAIT_END_OF_EXECUTION':
747 if self.cur_state == 'WAIT_END_OF_EXECUTION':
745 #we raise an exception inside the IPython thread container
748 #we raise an exception inside the IPython thread container
746 self.IP.ce.raise_exc(KeyboardInterrupt)
749 self.IP.ce.raise_exc(KeyboardInterrupt)
747 return
750 return
748
751
749 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
752 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
750 #mode if AutoComp has been set as inactive
753 #mode if AutoComp has been set as inactive
751 if self.cur_state == 'COMPLETING':
754 if self.cur_state == 'COMPLETING':
752 if not self.text_ctrl.AutoCompActive():
755 if not self.text_ctrl.AutoCompActive():
753 self.cur_state = 'IDLE'
756 self.cur_state = 'IDLE'
754 else:
757 else:
755 event.Skip()
758 event.Skip()
756
759
757 if event.KeyCode == wx.WXK_RETURN:
760 if event.KeyCode == wx.WXK_RETURN:
758 if self.cur_state == 'IDLE':
761 if self.cur_state == 'IDLE':
759 #we change the state ot the state machine
762 #we change the state ot the state machine
760 self.setCurrentState('DO_EXECUTE_LINE')
763 self.setCurrentState('DO_EXECUTE_LINE')
761 self.stateDoExecuteLine()
764 self.stateDoExecuteLine()
762 return
765 return
763
766
764 if self.pager_state == 'WAITING':
767 if self.pager_state == 'WAITING':
765 self.pager_state = 'PROCESS_LINES'
768 self.pager_state = 'PROCESS_LINES'
766 self.pager(self.doc)
769 self.pager(self.doc)
767 return
770 return
768
771
769 if self.cur_state == 'WAITING_USER_INPUT':
772 if self.cur_state == 'WAITING_USER_INPUT':
770 line=self.text_ctrl.getCurrentLine()
773 line=self.text_ctrl.getCurrentLine()
771 self.text_ctrl.write('\n')
774 self.text_ctrl.write('\n')
772 self.setCurrentState('WAIT_END_OF_EXECUTION')
775 self.setCurrentState('WAIT_END_OF_EXECUTION')
773 return
776 return
774
777
775 if event.GetKeyCode() in [ord('q'),ord('Q')]:
778 if event.GetKeyCode() in [ord('q'),ord('Q')]:
776 if self.pager_state == 'WAITING':
779 if self.pager_state == 'WAITING':
777 self.pager_state = 'DONE'
780 self.pager_state = 'DONE'
778 self.text_ctrl.write('\n')
781 self.text_ctrl.write('\n')
779 self.stateShowPrompt()
782 self.stateShowPrompt()
780 return
783 return
781
784
782 if self.cur_state == 'WAITING_USER_INPUT':
785 if self.cur_state == 'WAITING_USER_INPUT':
783 event.Skip()
786 event.Skip()
784
787
785 if self.cur_state == 'IDLE':
788 if self.cur_state == 'IDLE':
786 if event.KeyCode == wx.WXK_UP:
789 if event.KeyCode == wx.WXK_UP:
787 history = self.IP.history_back()
790 history = self.IP.history_back()
788 self.text_ctrl.writeHistory(history)
791 self.text_ctrl.writeHistory(history)
789 return
792 return
790 if event.KeyCode == wx.WXK_DOWN:
793 if event.KeyCode == wx.WXK_DOWN:
791 history = self.IP.history_forward()
794 history = self.IP.history_forward()
792 self.text_ctrl.writeHistory(history)
795 self.text_ctrl.writeHistory(history)
793 return
796 return
794 if event.KeyCode == wx.WXK_TAB:
797 if event.KeyCode == wx.WXK_TAB:
795 #if line empty we disable tab completion
798 #if line empty we disable tab completion
796 if not self.text_ctrl.getCurrentLine().strip():
799 if not self.text_ctrl.getCurrentLine().strip():
797 self.text_ctrl.write('\t')
800 self.text_ctrl.write('\t')
798 return
801 return
799 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
802 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
800 if len(possibilities) > 1:
803 if len(possibilities) > 1:
801 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
804 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
802 cur_slice = self.text_ctrl.getCurrentLine()
805 cur_slice = self.text_ctrl.getCurrentLine()
803 self.text_ctrl.write('\n')
806 self.text_ctrl.write('\n')
804 self.text_ctrl.writeCompletion(possibilities)
807 self.text_ctrl.writeCompletion(possibilities)
805 self.text_ctrl.write('\n')
808 self.text_ctrl.write('\n')
806
809
807 self.text_ctrl.showPrompt()
810 self.text_ctrl.showPrompt()
808 self.text_ctrl.write(cur_slice)
811 self.text_ctrl.write(cur_slice)
809 self.text_ctrl.changeLine(completed or cur_slice)
812 self.text_ctrl.changeLine(completed or cur_slice)
810 else:
813 else:
811 self.cur_state = 'COMPLETING'
814 self.cur_state = 'COMPLETING'
812 self.text_ctrl.writeCompletion(possibilities)
815 self.text_ctrl.writeCompletion(possibilities)
813 else:
816 else:
814 self.text_ctrl.changeLine(completed or cur_slice)
817 self.text_ctrl.changeLine(completed or cur_slice)
815 return
818 return
816 event.Skip()
819 event.Skip()
817
820
818 #------------------------ Option Section ---------------------------------
821 #------------------------ Option Section ---------------------------------
819 def evtCheckOptionCompletion(self, event):
822 def evtCheckOptionCompletion(self, event):
820 if event.IsChecked():
823 if event.IsChecked():
821 self.options['completion']['value']='STC'
824 self.options['completion']['value']='STC'
822 else:
825 else:
823 self.options['completion']['value']='IPYTHON'
826 self.options['completion']['value']='IPYTHON'
824 self.text_ctrl.setCompletionMethod(self.options['completion']['value'])
827 self.text_ctrl.setCompletionMethod(self.options['completion']['value'])
825 self.updateOptionTracker('completion',
828 self.updateOptionTracker('completion',
826 self.options['completion']['value'])
829 self.options['completion']['value'])
827 self.text_ctrl.SetFocus()
830 self.text_ctrl.SetFocus()
828
831
829 def evtCheckOptionBackgroundColor(self, event):
832 def evtCheckOptionBackgroundColor(self, event):
830 if event.IsChecked():
833 if event.IsChecked():
831 self.options['background_color']['value']='WHITE'
834 self.options['background_color']['value']='WHITE'
832 else:
835 else:
833 self.options['background_color']['value']='BLACK'
836 self.options['background_color']['value']='BLACK'
834 self.text_ctrl.setBackgroundColor(self.options['background_color']['value'])
837 self.text_ctrl.setBackgroundColor(self.options['background_color']['value'])
835 self.updateOptionTracker('background_color',
838 self.updateOptionTracker('background_color',
836 self.options['background_color']['value'])
839 self.options['background_color']['value'])
837 self.text_ctrl.SetFocus()
840 self.text_ctrl.SetFocus()
838
841
839 def evtCheckOptionThreading(self, event):
842 def evtCheckOptionThreading(self, event):
840 if event.IsChecked():
843 if event.IsChecked():
841 self.options['threading']['value']='True'
844 self.options['threading']['value']='True'
842 self.IP.set_threading(True)
845 self.IP.set_threading(True)
843 self.cout.write = self.text_ctrl.asyncWrite
846 self.cout.write = self.text_ctrl.asyncWrite
844 else:
847 else:
845 self.options['threading']['value']='False'
848 self.options['threading']['value']='False'
846 self.IP.set_threading(False)
849 self.IP.set_threading(False)
847 self.cout.write = self.text_ctrl.write
850 self.cout.write = self.text_ctrl.write
848 self.updateOptionTracker('threading',
851 self.updateOptionTracker('threading',
849 self.options['threading']['value'])
852 self.options['threading']['value'])
850 self.text_ctrl.SetFocus()
853 self.text_ctrl.SetFocus()
851
854
852 def getOptions(self):
855 def getOptions(self):
853 return self.options
856 return self.options
854
857
855 def reloadOptions(self,options):
858 def reloadOptions(self,options):
856 self.options = options
859 self.options = options
857 for key in self.options.keys():
860 for key in self.options.keys():
858 value = self.options[key]['value']
861 value = self.options[key]['value']
859 self.options[key]['checkbox'].SetValue(self.options[key][value])
862 self.options[key]['checkbox'].SetValue(self.options[key][value])
860 self.options[key]['setfunc'](value)
863 self.options[key]['setfunc'](value)
861
864
862 if self.options['threading']['value']=='True':
865 if self.options['threading']['value']=='True':
863 self.IP.set_threading(True)
866 self.IP.set_threading(True)
864 self.cout.write = self.text_ctrl.asyncWrite
867 self.cout.write = self.text_ctrl.asyncWrite
865 else:
868 else:
866 self.IP.set_threading(False)
869 self.IP.set_threading(False)
867 self.cout.write = self.text_ctrl.write
870 self.cout.write = self.text_ctrl.write
868
871
869 #------------------------ Hook Section -----------------------------------
872 #------------------------ Hook Section -----------------------------------
870 def updateOptionTracker(self,name,value):
873 def updateOptionTracker(self,name,value):
871 '''
874 '''
872 Default history tracker (does nothing)
875 Default history tracker (does nothing)
873 '''
876 '''
874 pass
877 pass
875
878
876 def setOptionTrackerHook(self,func):
879 def setOptionTrackerHook(self,func):
877 '''
880 '''
878 Define a new history tracker
881 Define a new history tracker
879 '''
882 '''
880 self.updateOptionTracker = func
883 self.updateOptionTracker = func
881
884
882 def updateHistoryTracker(self,command_line):
885 def updateHistoryTracker(self,command_line):
883 '''
886 '''
884 Default history tracker (does nothing)
887 Default history tracker (does nothing)
885 '''
888 '''
886 pass
889 pass
887
890
888 def setHistoryTrackerHook(self,func):
891 def setHistoryTrackerHook(self,func):
889 '''
892 '''
890 Define a new history tracker
893 Define a new history tracker
891 '''
894 '''
892 self.updateHistoryTracker = func
895 self.updateHistoryTracker = func
893
896
894 def updateStatusTracker(self,status):
897 def updateStatusTracker(self,status):
895 '''
898 '''
896 Default status tracker (does nothing)
899 Default status tracker (does nothing)
897 '''
900 '''
898 pass
901 pass
899
902
900 def setStatusTrackerHook(self,func):
903 def setStatusTrackerHook(self,func):
901 '''
904 '''
902 Define a new status tracker
905 Define a new status tracker
903 '''
906 '''
904 self.updateStatusTracker = func
907 self.updateStatusTracker = func
905
908
906 def askExitHandler(self, event):
909 def askExitHandler(self, event):
907 '''
910 '''
908 Default exit handler
911 Default exit handler
909 '''
912 '''
910 self.text_ctrl.write('\nExit callback has not been set.')
913 self.text_ctrl.write('\nExit callback has not been set.')
911
914
912 def setAskExitHandler(self, func):
915 def setAskExitHandler(self, func):
913 '''
916 '''
914 Define an exit handler
917 Define an exit handler
915 '''
918 '''
916 self.askExitHandler = func
919 self.askExitHandler = func
917
920
918 if __name__ == '__main__':
921 if __name__ == '__main__':
919 # Some simple code to test the shell widget.
922 # Some simple code to test the shell widget.
920 class MainWindow(wx.Frame):
923 class MainWindow(wx.Frame):
921 def __init__(self, parent, id, title):
924 def __init__(self, parent, id, title):
922 wx.Frame.__init__(self, parent, id, title, size=(300,250))
925 wx.Frame.__init__(self, parent, id, title, size=(300,250))
923 self._sizer = wx.BoxSizer(wx.VERTICAL)
926 self._sizer = wx.BoxSizer(wx.VERTICAL)
924 self.shell = IPShellWidget(self)
927 self.shell = IPShellWidget(self)
925 self._sizer.Add(self.shell, 1, wx.EXPAND)
928 self._sizer.Add(self.shell, 1, wx.EXPAND)
926 self.SetSizer(self._sizer)
929 self.SetSizer(self._sizer)
927 self.SetAutoLayout(1)
930 self.SetAutoLayout(1)
928 self.Show(True)
931 self.Show(True)
929
932
930 app = wx.PySimpleApp()
933 app = wx.PySimpleApp()
931 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
934 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
932 frame.SetSize((780, 460))
935 frame.SetSize((780, 460))
933 shell = frame.shell
936 shell = frame.shell
934
937
935 app.MainLoop()
938 app.MainLoop()
936
939
937
940
General Comments 0
You need to be logged in to leave comments. Login now