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