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