##// END OF EJS Templates
First hook of the widget to the interpreter.
gvaroquaux -
Show More
@@ -27,6 +27,40 b' import re'
27 # FIXME: Need to provide an API for non user-generated display on the
27 # FIXME: Need to provide an API for non user-generated display on the
28 # screen: this should not be editable by the user.
28 # screen: this should not be editable by the user.
29
29
30 if wx.Platform == '__WXMSW__':
31 _DEFAULT_SIZE = 80
32 else:
33 _DEFAULT_SIZE = 10
34
35 _DEFAULT_STYLE = {
36 'stdout' : 'fore:#0000FF',
37 'stderr' : 'fore:#007f00',
38 'trace' : 'fore:#FF0000',
39
40 'default' : 'size:%d' % _DEFAULT_SIZE,
41 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
42 'bracebad' : 'fore:#000000,back:#FF0000,bold',
43
44 # properties for the various Python lexer styles
45 'comment' : 'fore:#007F00',
46 'number' : 'fore:#007F7F',
47 'string' : 'fore:#7F007F,italic',
48 'char' : 'fore:#7F007F,italic',
49 'keyword' : 'fore:#00007F,bold',
50 'triple' : 'fore:#7F0000',
51 'tripledouble': 'fore:#7F0000',
52 'class' : 'fore:#0000FF,bold,underline',
53 'def' : 'fore:#007F7F,bold',
54 'operator' : 'bold',
55
56 }
57
58 # new style numbers
59 _STDOUT_STYLE = 15
60 _STDERR_STYLE = 16
61 _TRACE_STYLE = 17
62
63
30 #-------------------------------------------------------------------------------
64 #-------------------------------------------------------------------------------
31 # The console widget class
65 # The console widget class
32 #-------------------------------------------------------------------------------
66 #-------------------------------------------------------------------------------
@@ -37,6 +71,8 b' class ConsoleWidget(stc.StyledTextCtrl):'
37 keeping the cursor inside the editing line.
71 keeping the cursor inside the editing line.
38 """
72 """
39
73
74 style = _DEFAULT_STYLE.copy()
75
40 # Translation table from ANSI escape sequences to color. Override
76 # Translation table from ANSI escape sequences to color. Override
41 # this to specify your colors.
77 # this to specify your colors.
42 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
78 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
@@ -57,6 +93,88 b' class ConsoleWidget(stc.StyledTextCtrl):'
57 # Public API
93 # Public API
58 #--------------------------------------------------------------------------
94 #--------------------------------------------------------------------------
59
95
96 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
97 size=wx.DefaultSize, style=0,
98 autocomplete_mode='IPYTHON'):
99 """ Autocomplete_mode: Can be 'IPYTHON' or 'STC'
100 'IPYTHON' show autocompletion the ipython way
101 'STC" show it scintilla text control way
102 """
103 stc.StyledTextCtrl.__init__(self, parent, id, pos, size, style)
104 self.configure_scintilla()
105
106 # FIXME: we need to retrieve this from the interpreter.
107 self.prompt = \
108 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
109 self.new_prompt(self.prompt % 1)
110
111 self.autocomplete_mode = autocomplete_mode
112
113 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
114
115
116 def configure_scintilla(self):
117 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
118 # the widget
119 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
120 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
121 # Also allow Ctrl Shift "=" for poor non US keyboard users.
122 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
123 stc.STC_CMD_ZOOMIN)
124
125 self.SetEOLMode(stc.STC_EOL_CRLF)
126 self.SetWrapMode(stc.STC_WRAP_CHAR)
127 self.SetWrapMode(stc.STC_WRAP_WORD)
128 self.SetBufferedDraw(True)
129 self.SetUseAntiAliasing(True)
130 self.SetLayoutCache(stc.STC_CACHE_PAGE)
131 self.SetUndoCollection(False)
132 self.SetUseTabs(True)
133 self.SetIndent(4)
134 self.SetTabWidth(4)
135
136 self.EnsureCaretVisible()
137
138 self.SetMargins(3, 3) #text is moved away from border with 3px
139 # Suppressing Scintilla margins
140 self.SetMarginWidth(0, 0)
141 self.SetMarginWidth(1, 0)
142 self.SetMarginWidth(2, 0)
143
144 self._apply_style()
145
146 self.indent = 0
147 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
148
149 #self.SetEdgeMode(stc.STC_EDGE_LINE)
150 #self.SetEdgeColumn(80)
151
152 # styles
153 p = self.style
154 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
155 self.StyleClearAll()
156 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
157 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
158 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
159
160 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
161 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
162 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
163 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
164 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
165 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
166 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
167 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
168 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
169 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
170 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
171 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
172 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
173
174
175
176
177
60 def write(self, text):
178 def write(self, text):
61 """ Write given text to buffer, while translating the ansi escape
179 """ Write given text to buffer, while translating the ansi escape
62 sequences.
180 sequences.
@@ -118,59 +236,6 b' class ConsoleWidget(stc.StyledTextCtrl):'
118 # Private API
236 # Private API
119 #--------------------------------------------------------------------------
237 #--------------------------------------------------------------------------
120
238
121 def __init__(self, parent, pos=wx.DefaultPosition, ID=-1,
122 size=wx.DefaultSize, style=0,
123 autocomplete_mode='IPYTHON'):
124 """ Autocomplete_mode: Can be 'IPYTHON' or 'STC'
125 'IPYTHON' show autocompletion the ipython way
126 'STC" show it scintilla text control way
127 """
128 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
129
130 #------ Scintilla configuration -----------------------------------
131
132 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
133 # the widget
134 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
135 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
136 # Also allow Ctrl Shift "=" for poor non US keyboard users.
137 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
138 stc.STC_CMD_ZOOMIN)
139
140 self.SetEOLMode(stc.STC_EOL_CRLF)
141 self.SetWrapMode(stc.STC_WRAP_CHAR)
142 self.SetWrapMode(stc.STC_WRAP_WORD)
143 self.SetBufferedDraw(True)
144 self.SetUseAntiAliasing(True)
145 self.SetLayoutCache(stc.STC_CACHE_PAGE)
146 self.SetUndoCollection(False)
147 self.SetUseTabs(True)
148 self.SetIndent(4)
149 self.SetTabWidth(4)
150
151 self.EnsureCaretVisible()
152
153 self.SetMargins(3, 3) #text is moved away from border with 3px
154 # Suppressing Scintilla margins
155 self.SetMarginWidth(0, 0)
156 self.SetMarginWidth(1, 0)
157 self.SetMarginWidth(2, 0)
158
159 self._apply_style()
160
161 self.indent = 0
162 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
163
164 # FIXME: we need to retrieve this from the interpreter.
165 self.prompt = \
166 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x026\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
167 self.new_prompt(self.prompt)
168
169 self.autocomplete_mode = autocomplete_mode
170
171 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
172
173
174 def _apply_style(self):
239 def _apply_style(self):
175 """ Applies the colors for the different text elements and the
240 """ Applies the colors for the different text elements and the
176 carret.
241 carret.
@@ -220,7 +285,7 b' class ConsoleWidget(stc.StyledTextCtrl):'
220 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
285 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
221
286
222
287
223 def removeFromTo(self, from_pos, to_pos):
288 def removeFromTo(self, from_pos, to_pos):
224 if from_pos < to_pos:
289 if from_pos < to_pos:
225 self.SetSelection(from_pos, to_pos)
290 self.SetSelection(from_pos, to_pos)
226 self.DeleteBack()
291 self.DeleteBack()
@@ -278,7 +343,7 b' class ConsoleWidget(stc.StyledTextCtrl):'
278 if self.AutoCompActive():
343 if self.AutoCompActive():
279 event.Skip()
344 event.Skip()
280 else:
345 else:
281 if event.GetKeyCode() == wx.WXK_HOME:
346 if event.KeyCode == wx.WXK_HOME:
282 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
347 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
283 self.GotoPos(self.current_prompt_pos)
348 self.GotoPos(self.current_prompt_pos)
284 catched = True
349 catched = True
@@ -288,7 +353,7 b' class ConsoleWidget(stc.StyledTextCtrl):'
288 self.GetCurrentPos())
353 self.GetCurrentPos())
289 catched = True
354 catched = True
290
355
291 elif event.GetKeyCode() == wx.WXK_UP:
356 elif event.KeyCode == wx.WXK_UP:
292 if self.GetCurrentLine() > self.current_prompt_line:
357 if self.GetCurrentLine() > self.current_prompt_line:
293 if self.GetCurrentLine() == self.current_prompt_line + 1 \
358 if self.GetCurrentLine() == self.current_prompt_line + 1 \
294 and self.GetColumn(self.GetCurrentPos()) < \
359 and self.GetColumn(self.GetCurrentPos()) < \
@@ -298,7 +363,7 b' class ConsoleWidget(stc.StyledTextCtrl):'
298 event.Skip()
363 event.Skip()
299 catched = True
364 catched = True
300
365
301 elif event.GetKeyCode() in (wx.WXK_LEFT, wx.WXK_BACK):
366 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
302 if self.GetCurrentPos() > self.current_prompt_pos:
367 if self.GetCurrentPos() > self.current_prompt_pos:
303 event.Skip()
368 event.Skip()
304 catched = True
369 catched = True
@@ -306,7 +371,7 b' class ConsoleWidget(stc.StyledTextCtrl):'
306 if skip and not catched:
371 if skip and not catched:
307 event.Skip()
372 event.Skip()
308
373
309 if event.GetKeyCode() not in (wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)\
374 if event.KeyCode not in (wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)\
310 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
375 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
311 wx.MOD_SHIFT):
376 wx.MOD_SHIFT):
312 # If cursor is outside the editing region, put it back.
377 # If cursor is outside the editing region, put it back.
@@ -25,42 +25,45 b' from console_widget import ConsoleWidget'
25
25
26
26
27 import IPython
27 import IPython
28 from IPython.kernel.engineservice import EngineService, ThreadedEngineService
28 from IPython.kernel.engineservice import EngineService
29 from IPython.frontend.frontendbase import FrontEndBase
29 from IPython.frontend.frontendbase import FrontEndBase
30
30
31
31
32 from twisted.internet.threads import blockingCallFromThread
33
34 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
35 # Classes to implement the Wx frontend
33 # Classes to implement the Wx frontend
36 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
37
35
38 # TODO:
36
39 # 1. Remove any multithreading.
40
41
37
42
38
43 class IPythonWxController(FrontEndBase, ConsoleWidget):
39 class IPythonWxController(FrontEndBase, ConsoleWidget):
44 userNS = dict() #mirror of engine.user_ns (key=>str(value))
40
45 waiting_for_engine = False
41 output_prompt = \
46 textView = False
42 '\n\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
47
43
44 #--------------------------------------------------------------------------
45 # Public API
46 #--------------------------------------------------------------------------
47
48 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
48 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
49 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
49 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
50 *args, **kwds):
50 *args, **kwds):
51 """ Create Shell instance.
51 """ Create Shell instance.
52 """
52 """
53 ConsoleWidget.__init__(self, parent, id, pos, size, style)
53 ConsoleWidget.__init__(self, parent, id, pos, size, style)
54 FrontEndBase.__init__(self, engine=ThreadedEngineService())
54 FrontEndBase.__init__(self, engine=EngineService())
55
55
56 self.lines = {}
56 self.lines = {}
57
57
58 # Start the IPython engine
58 # Start the IPython engine
59 self.engine.startService()
59 self.engine.startService()
60
60
61 # Capture Character keys
62 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
61
63
62 #FIXME: print banner.
64 #FIXME: print banner.
63 banner = """IPython1 %s -- An enhanced Interactive Python.""" % IPython.__version__
65 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
66 % IPython.__version__
64
67
65
68
66 def appWillTerminate_(self, notification):
69 def appWillTerminate_(self, notification):
@@ -85,168 +88,12 b' class IPythonWxController(FrontEndBase, ConsoleWidget):'
85 return self.engine.complete(token)
88 return self.engine.complete(token)
86
89
87
90
88 def execute(self, block, blockID=None):
89 self.waiting_for_engine = True
90 self.willChangeValueForKey_('commandHistory')
91 d = FrontEndBase.execute(block, blockID)
92 d.addBoth(self._engine_done)
93
94 return d
95
96
97 def _engine_done(self, x):
98 self.waiting_for_engine = False
99 self.didChangeValueForKey_('commandHistory')
100 return x
101
102
103 def _store_engine_namespace_values(self, values, keys=[]):
104 assert(len(values) == len(keys))
105 self.willChangeValueForKey_('userNS')
106 for (k,v) in zip(keys,values):
107 self.userNS[k] = saferepr(v)
108 self.didChangeValueForKey_('userNS')
109
110
111 def textView_doCommandBySelector_(self, textView, selector):
112 assert(textView == self.textView)
113 NSLog("textView_doCommandBySelector_: "+selector)
114
115
116 if(selector == 'insertNewline:'):
117 indent = self.current_indent_string()
118 if(indent):
119 line = indent + self.current_line()
120 else:
121 line = self.current_line()
122
123 if(self.is_complete(self.current_block())):
124 self.execute(self.current_block(),
125 blockID=self.currentBlockID)
126 self.start_new_block()
127
128 return True
129
130 return False
131
132 elif(selector == 'moveUp:'):
133 prevBlock = self.get_history_previous(self.current_block())
134 if(prevBlock != None):
135 self.replace_current_block_with_string(textView, prevBlock)
136 else:
137 NSBeep()
138 return True
139
140 elif(selector == 'moveDown:'):
141 nextBlock = self.get_history_next()
142 if(nextBlock != None):
143 self.replace_current_block_with_string(textView, nextBlock)
144 else:
145 NSBeep()
146 return True
147
148 elif(selector == 'moveToBeginningOfParagraph:'):
149 textView.setSelectedRange_(NSMakeRange(
150 self.current_block_range().location,
151 0))
152 return True
153 elif(selector == 'moveToEndOfParagraph:'):
154 textView.setSelectedRange_(NSMakeRange(
155 self.current_block_range().location + \
156 self.current_block_range().length, 0))
157 return True
158 elif(selector == 'deleteToEndOfParagraph:'):
159 if(textView.selectedRange().location <= \
160 self.current_block_range().location):
161 # Intersect the selected range with the current line range
162 if(self.current_block_range().length < 0):
163 self.blockRanges[self.currentBlockID].length = 0
164
165 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
166 self.current_block_range())
167
168 if(r.length > 0): #no intersection
169 textView.setSelectedRange_(r)
170
171 return False # don't actually handle the delete
172
173 elif(selector == 'insertTab:'):
174 if(len(self.current_line().strip()) == 0): #only white space
175 return False
176 else:
177 self.textView.complete_(self)
178 return True
179
180 elif(selector == 'deleteBackward:'):
181 #if we're at the beginning of the current block, ignore
182 if(textView.selectedRange().location == \
183 self.current_block_range().location):
184 return True
185 else:
186 self.current_block_range().length-=1
187 return False
188 return False
189
190
191 def textView_shouldChangeTextInRanges_replacementStrings_(self,
192 textView, ranges, replacementStrings):
193 """
194 Delegate method for NSTextView.
195
196 Refuse change text in ranges not at end, but make those changes at
197 end.
198 """
199
200 assert(len(ranges) == len(replacementStrings))
201 allow = True
202 for r,s in zip(ranges, replacementStrings):
203 r = r.rangeValue()
204 if(textView.textStorage().length() > 0 and
205 r.location < self.current_block_range().location):
206 self.insert_text(s)
207 allow = False
208
209
210 self.blockRanges.setdefault(self.currentBlockID,
211 self.current_block_range()).length +=\
212 len(s)
213
214 return allow
215
216
217 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
218 textView, words, charRange, index):
219 try:
220 ts = textView.textStorage()
221 token = ts.string().substringWithRange_(charRange)
222 completions = blockingCallFromThread(self.complete, token)
223 except:
224 completions = objc.nil
225 NSBeep()
226
227 return (completions,0)
228
229
230 def currentLine(self):
231 block = self.textForRange(self.currentBlockRange())
232 block = block.split('\n')
233 return block[-1]
234
235 def update_cell_prompt(self, result):
236 blockID = result['blockID']
237 self.insert_text(self.inputPrompt(result=result),
238 scrollToVisible=False
239 )
240
241 return result
242
243
244 def render_result(self, result):
91 def render_result(self, result):
245 self.insert_text('\n' +
92 if 'stdout' in result:
246 self.outputPrompt(result) +
93 self.write(result['stdout'])
247 result.get('display',{}).get('pprint','') +
94 if 'display' in result:
248 '\n\n')
95 self.write(self.output_prompt % result['number']
249 return result
96 + result['display']['pprint'])
250
97
251
98
252 def render_error(self, failure):
99 def render_error(self, failure):
@@ -254,26 +101,30 b' class IPythonWxController(FrontEndBase, ConsoleWidget):'
254 return failure
101 return failure
255
102
256
103
257 def insert_text(self, string, scrollToVisible=True):
104 #--------------------------------------------------------------------------
258 """Insert text into console_widget"""
105 # Private API
259 self.write(string)
106 #--------------------------------------------------------------------------
260
107
261
108
262 def currentIndentString(self):
109 def _on_key_up(self, event, skip=True):
263 """returns string for indent or None if no indent"""
110 """ Capture the character events, let the parent
264
111 widget handle them, and put our logic afterward.
265 if(len(self.currentBlock()) > 0):
112 """
266 lines = self.currentBlock().split('\n')
113 event.Skip()
267 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
114 # Capture enter
268 if(currentIndent == 0):
115 if event.KeyCode == 13 and \
269 currentIndent = 4
116 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
270
117 self._on_enter()
271 result = ' ' * currentIndent
118
272 else:
273 result = None
274
275 return result
276
119
120 def _on_enter(self):
121 """ Called when the return key is pressed in a line editing
122 buffer.
123 """
124 result = self.engine.shell.execute(self.get_current_edit_buffer())
125 self.render_result(result)
126 self.new_prompt(self.prompt % result['number'])
127
277
128
278 if __name__ == '__main__':
129 if __name__ == '__main__':
279 class MainWindow(wx.Frame):
130 class MainWindow(wx.Frame):
@@ -288,8 +139,9 b" if __name__ == '__main__':"
288
139
289 app = wx.PySimpleApp()
140 app = wx.PySimpleApp()
290 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
141 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
142 frame.shell.SetFocus()
291 frame.SetSize((780, 460))
143 frame.SetSize((780, 460))
292 shell = frame.shell
144 shell = frame.shell
293
145
294 app.MainLoop()
146 #app.MainLoop()
295
147
General Comments 0
You need to be logged in to leave comments. Login now