##// END OF EJS Templates
Full patch for better user color tweaking + patch for 'enter' synchro bug
Laurent Dufrechou -
Show More
@@ -1,251 +1,250 b''
1 1 """
2 2 Frontend class that uses IPython0 to prefilter the inputs.
3 3
4 4 Using the IPython0 mechanism gives us access to the magics.
5 5
6 6 This is a transitory class, used here to do the transition between
7 7 ipython0 and ipython1. This class is meant to be short-lived as more
8 8 functionnality is abstracted out of ipython0 in reusable functions and
9 9 is added on the interpreter. This class can be a used to guide this
10 10 refactoring.
11 11 """
12 12 __docformat__ = "restructuredtext en"
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-------------------------------------------------------------------------------
20 20
21 21 #-------------------------------------------------------------------------------
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24 import sys
25 25
26 26 from linefrontendbase import LineFrontEndBase, common_prefix
27 27 from frontendbase import FrontEndBase
28 28
29 29 from IPython.ipmaker import make_IPython
30 30 from IPython.ipapi import IPApi
31 31 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
32 32
33 33 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34 34
35 35 from IPython.genutils import Term
36 36 import pydoc
37 37 import os
38 38 import sys
39 39
40 40
41 41 def mk_system_call(system_call_function, command):
42 42 """ given a os.system replacement, and a leading string command,
43 43 returns a function that will execute the command with the given
44 44 argument string.
45 45 """
46 46 def my_system_call(args):
47 47 system_call_function("%s %s" % (command, args))
48 48
49 49 my_system_call.__doc__ = "Calls %s" % command
50 50 return my_system_call
51 51
52 52 #-------------------------------------------------------------------------------
53 53 # Frontend class using ipython0 to do the prefiltering.
54 54 #-------------------------------------------------------------------------------
55 55 class PrefilterFrontEnd(LineFrontEndBase):
56 56 """ Class that uses ipython0 to do prefilter the input, do the
57 57 completion and the magics.
58 58
59 59 The core trick is to use an ipython0 instance to prefilter the
60 60 input, and share the namespace between the interpreter instance used
61 61 to execute the statements and the ipython0 used for code
62 62 completion...
63 63 """
64 64
65 65 debug = False
66 66
67 67 def __init__(self, ipython0=None, *args, **kwargs):
68 68 """ Parameters:
69 69 -----------
70 70
71 71 ipython0: an optional ipython0 instance to use for command
72 72 prefiltering and completion.
73 73 """
74 74 LineFrontEndBase.__init__(self, *args, **kwargs)
75 75 self.shell.output_trap = RedirectorOutputTrap(
76 76 out_callback=self.write,
77 77 err_callback=self.write,
78 78 )
79 79 self.shell.traceback_trap = SyncTracebackTrap(
80 80 formatters=self.shell.traceback_trap.formatters,
81 81 )
82 82
83 83 # Start the ipython0 instance:
84 84 self.save_output_hooks()
85 85 if ipython0 is None:
86 86 # Instanciate an IPython0 interpreter to be able to use the
87 87 # prefiltering.
88 88 # XXX: argv=[] is a bit bold.
89 89 ipython0 = make_IPython(argv=[],
90 90 user_ns=self.shell.user_ns,
91 91 user_global_ns=self.shell.user_global_ns)
92 92 self.ipython0 = ipython0
93 93 # Set the pager:
94 94 self.ipython0.set_hook('show_in_pager',
95 95 lambda s, string: self.write("\n" + string))
96 96 self.ipython0.write = self.write
97 97 self._ip = _ip = IPApi(self.ipython0)
98 98 # Make sure the raw system call doesn't get called, as we don't
99 99 # have a stdin accessible.
100 100 self._ip.system = self.system_call
101 101 # XXX: Muck around with magics so that they work better
102 102 # in our environment
103 103 self.ipython0.magic_ls = mk_system_call(self.system_call,
104 104 'ls -CF')
105 105 # And now clean up the mess created by ipython0
106 106 self.release_output()
107 107
108 108
109 109 if not 'banner' in kwargs and self.banner is None:
110 self.banner = self.ipython0.BANNER + """
111 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
110 self.banner = self.ipython0.BANNER
112 111
113 112 self.start()
114 113
115 114 #--------------------------------------------------------------------------
116 115 # FrontEndBase interface
117 116 #--------------------------------------------------------------------------
118 117
119 118 def show_traceback(self):
120 119 """ Use ipython0 to capture the last traceback and display it.
121 120 """
122 121 # Don't do the capture; the except_hook has already done some
123 122 # modifications to the IO streams, if we store them, we'll be
124 123 # storing the wrong ones.
125 124 #self.capture_output()
126 125 self.ipython0.showtraceback(tb_offset=-1)
127 126 self.release_output()
128 127
129 128
130 129 def execute(self, python_string, raw_string=None):
131 130 if self.debug:
132 131 print 'Executing Python code:', repr(python_string)
133 132 self.capture_output()
134 133 LineFrontEndBase.execute(self, python_string,
135 134 raw_string=raw_string)
136 135 self.release_output()
137 136
138 137
139 138 def save_output_hooks(self):
140 139 """ Store all the output hooks we can think of, to be able to
141 140 restore them.
142 141
143 142 We need to do this early, as starting the ipython0 instance will
144 143 screw ouput hooks.
145 144 """
146 145 self.__old_cout_write = Term.cout.write
147 146 self.__old_cerr_write = Term.cerr.write
148 147 self.__old_stdout = sys.stdout
149 148 self.__old_stderr= sys.stderr
150 149 self.__old_help_output = pydoc.help.output
151 150 self.__old_display_hook = sys.displayhook
152 151
153 152
154 153 def capture_output(self):
155 154 """ Capture all the output mechanisms we can think of.
156 155 """
157 156 self.save_output_hooks()
158 157 Term.cout.write = self.write
159 158 Term.cerr.write = self.write
160 159 sys.stdout = Term.cout
161 160 sys.stderr = Term.cerr
162 161 pydoc.help.output = self.shell.output_trap.out
163 162
164 163
165 164 def release_output(self):
166 165 """ Release all the different captures we have made.
167 166 """
168 167 Term.cout.write = self.__old_cout_write
169 168 Term.cerr.write = self.__old_cerr_write
170 169 sys.stdout = self.__old_stdout
171 170 sys.stderr = self.__old_stderr
172 171 pydoc.help.output = self.__old_help_output
173 172 sys.displayhook = self.__old_display_hook
174 173
175 174
176 175 def complete(self, line):
177 176 # FIXME: This should be factored out in the linefrontendbase
178 177 # method.
179 178 word = line.split('\n')[-1].split(' ')[-1]
180 179 completions = self.ipython0.complete(word)
181 180 # FIXME: The proper sort should be done in the complete method.
182 181 key = lambda x: x.replace('_', '')
183 182 completions.sort(key=key)
184 183 if completions:
185 184 prefix = common_prefix(completions)
186 185 line = line[:-len(word)] + prefix
187 186 return line, completions
188 187
189 188
190 189 #--------------------------------------------------------------------------
191 190 # LineFrontEndBase interface
192 191 #--------------------------------------------------------------------------
193 192
194 193 def prefilter_input(self, input_string):
195 194 """ Using IPython0 to prefilter the commands to turn them
196 195 in executable statements that are valid Python strings.
197 196 """
198 197 input_string = LineFrontEndBase.prefilter_input(self, input_string)
199 198 filtered_lines = []
200 199 # The IPython0 prefilters sometime produce output. We need to
201 200 # capture it.
202 201 self.capture_output()
203 202 self.last_result = dict(number=self.prompt_number)
204 203
205 204 ## try:
206 205 ## for line in input_string.split('\n'):
207 206 ## filtered_lines.append(
208 207 ## self.ipython0.prefilter(line, False).rstrip())
209 208 ## except:
210 209 ## # XXX: probably not the right thing to do.
211 210 ## self.ipython0.showsyntaxerror()
212 211 ## self.after_execute()
213 212 ## finally:
214 213 ## self.release_output()
215 214
216 215
217 216 try:
218 217 try:
219 218 for line in input_string.split('\n'):
220 219 filtered_lines.append(
221 220 self.ipython0.prefilter(line, False).rstrip())
222 221 except:
223 222 # XXX: probably not the right thing to do.
224 223 self.ipython0.showsyntaxerror()
225 224 self.after_execute()
226 225 finally:
227 226 self.release_output()
228 227
229 228
230 229
231 230 # Clean up the trailing whitespace, to avoid indentation errors
232 231 filtered_string = '\n'.join(filtered_lines)
233 232 return filtered_string
234 233
235 234
236 235 #--------------------------------------------------------------------------
237 236 # PrefilterFrontEnd interface
238 237 #--------------------------------------------------------------------------
239 238
240 239 def system_call(self, command_string):
241 240 """ Allows for frontend to define their own system call, to be
242 241 able capture output and redirect input.
243 242 """
244 243 return os.system(command_string)
245 244
246 245
247 246 def do_exit(self):
248 247 """ Exit the shell, cleanup and save the history.
249 248 """
250 249 self.ipython0.atexit_operations()
251 250
@@ -1,436 +1,512 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26 import time
27 27 import sys
28 28 LINESEP = '\n'
29 29 if sys.platform == 'win32':
30 30 LINESEP = '\n\r'
31 31
32 32 import re
33 33
34 34 # FIXME: Need to provide an API for non user-generated display on the
35 35 # screen: this should not be editable by the user.
36 #-------------------------------------------------------------------------------
37 # Constants
38 #-------------------------------------------------------------------------------
39 _COMPLETE_BUFFER_MARKER = 31
40 _ERROR_MARKER = 30
41 _INPUT_MARKER = 29
36 42
37 43 _DEFAULT_SIZE = 10
38 44 if sys.platform == 'darwin':
39 45 _DEFAULT_SIZE = 12
40 46
41 47 _DEFAULT_STYLE = {
42 48 'stdout' : 'fore:#0000FF',
43 49 'stderr' : 'fore:#007f00',
44 50 'trace' : 'fore:#FF0000',
45 51
46 52 'default' : 'size:%d' % _DEFAULT_SIZE,
47 53 'bracegood' : 'fore:#00AA00,back:#000000,bold',
48 54 'bracebad' : 'fore:#FF0000,back:#000000,bold',
49 55
50 56 # properties for the various Python lexer styles
51 57 'comment' : 'fore:#007F00',
52 58 'number' : 'fore:#007F7F',
53 59 'string' : 'fore:#7F007F,italic',
54 60 'char' : 'fore:#7F007F,italic',
55 61 'keyword' : 'fore:#00007F,bold',
56 62 'triple' : 'fore:#7F0000',
57 63 'tripledouble' : 'fore:#7F0000',
58 64 'class' : 'fore:#0000FF,bold,underline',
59 65 'def' : 'fore:#007F7F,bold',
60 66 'operator' : 'bold'
61 67 }
62 68
63 69 # new style numbers
64 70 _STDOUT_STYLE = 15
65 71 _STDERR_STYLE = 16
66 72 _TRACE_STYLE = 17
67 73
68 74
69 75 # system colors
70 76 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
71 77
72 78 #-------------------------------------------------------------------------------
73 79 # The console widget class
74 80 #-------------------------------------------------------------------------------
75 81 class ConsoleWidget(editwindow.EditWindow):
76 82 """ Specialized styled text control view for console-like workflow.
77 83
78 84 This widget is mainly interested in dealing with the prompt and
79 85 keeping the cursor inside the editing line.
80 86 """
81 87
82 88 # This is where the title captured from the ANSI escape sequences are
83 89 # stored.
84 90 title = 'Console'
85 91
86 92 # The buffer being edited.
87 93 def _set_input_buffer(self, string):
88 94 self.SetSelection(self.current_prompt_pos, self.GetLength())
89 95 self.ReplaceSelection(string)
90 96 self.GotoPos(self.GetLength())
91 97
92 98 def _get_input_buffer(self):
93 99 """ Returns the text in current edit buffer.
94 100 """
95 101 input_buffer = self.GetTextRange(self.current_prompt_pos,
96 102 self.GetLength())
97 103 input_buffer = input_buffer.replace(LINESEP, '\n')
98 104 return input_buffer
99 105
100 106 input_buffer = property(_get_input_buffer, _set_input_buffer)
101 107
102 108 style = _DEFAULT_STYLE.copy()
103 109
104 110 # Translation table from ANSI escape sequences to color. Override
105 111 # this to specify your colors.
106 112 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
107 113 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
108 114 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
109 115 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
110 116 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
111 117 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
112 '1;34': [12, 'LIGHT BLUE'], '1;35':
113 [13, 'MEDIUM VIOLET RED'],
118 '1;34': [12, 'LIGHT BLUE'], '1;35': [13, 'MEDIUM VIOLET RED'],
114 119 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
115 120
116 121 # The color of the carret (call _apply_style() after setting)
117 122 carret_color = 'BLACK'
118 123
124 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
125 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
126 _ERROR_BG = '#FFF1F1' # Nice red
127
128 background_color = 'WHITE'
129
130 #we define platform specific fonts
131 if wx.Platform == '__WXMSW__':
132 faces = { 'times': 'Times New Roman',
133 'mono' : 'Courier New',
134 'helv' : 'Arial',
135 'other': 'Comic Sans MS',
136 'size' : 10,
137 'size2': 8,
138 }
139 elif wx.Platform == '__WXMAC__':
140 faces = { 'times': 'Times New Roman',
141 'mono' : 'Monaco',
142 'helv' : 'Arial',
143 'other': 'Comic Sans MS',
144 'size' : 10,
145 'size2': 8,
146 }
147 else:
148 faces = { 'times': 'Times',
149 'mono' : 'Courier',
150 'helv' : 'Helvetica',
151 'other': 'new century schoolbook',
152 'size' : 10,
153 'size2': 8,
154 }
155
119 156 # Store the last time a refresh was done
120 157 _last_refresh_time = 0
121 158
122 159 #--------------------------------------------------------------------------
123 160 # Public API
124 161 #--------------------------------------------------------------------------
125 162
126 163 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
127 164 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 165 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
129 166 self._configure_scintilla()
167 self.enter_catched = False #this var track if 'enter' key as ever been processed
168 #thus it will only be reallowed until key goes up
169 self.current_prompt_pos = 0
130 170
131 171 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 172 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
133 173
134 174
135 175 def write(self, text, refresh=True):
136 176 """ Write given text to buffer, while translating the ansi escape
137 177 sequences.
138 178 """
139 179 # XXX: do not put print statements to sys.stdout/sys.stderr in
140 180 # this method, the print statements will call this method, as
141 181 # you will end up with an infinit loop
142 182 title = self.title_pat.split(text)
143 183 if len(title)>1:
144 184 self.title = title[-2]
145 185
146 186 text = self.title_pat.sub('', text)
147 187 segments = self.color_pat.split(text)
148 188 segment = segments.pop(0)
149 189 self.GotoPos(self.GetLength())
150 190 self.StartStyling(self.GetLength(), 0xFF)
151 191 try:
152 192 self.AppendText(segment)
153 193 except UnicodeDecodeError:
154 194 # XXX: Do I really want to skip the exception?
155 195 pass
156 196
157 197 if segments:
158 198 for ansi_tag, text in zip(segments[::2], segments[1::2]):
159 199 self.StartStyling(self.GetLength(), 0xFF)
160 200 try:
161 201 self.AppendText(text)
162 202 except UnicodeDecodeError:
163 203 # XXX: Do I really want to skip the exception?
164 204 pass
165 205
166 206 if ansi_tag not in self.ANSI_STYLES:
167 207 style = 0
168 208 else:
169 209 style = self.ANSI_STYLES[ansi_tag][0]
170 210
171 211 self.SetStyling(len(text), style)
172 212
173 213 self.GotoPos(self.GetLength())
174 214 if refresh:
175 215 current_time = time.time()
176 216 if current_time - self._last_refresh_time > 0.03:
177 217 if sys.platform == 'win32':
178 218 wx.SafeYield()
179 219 else:
180 220 wx.Yield()
181 221 # self.ProcessEvent(wx.PaintEvent())
182 222 self._last_refresh_time = current_time
183 223
184 224
185 225 def new_prompt(self, prompt):
186 226 """ Prints a prompt at start of line, and move the start of the
187 227 current block there.
188 228
189 229 The prompt can be given with ascii escape sequences.
190 230 """
191 231 self.write(prompt, refresh=False)
192 232 # now we update our cursor giving end of prompt
193 233 self.current_prompt_pos = self.GetLength()
194 234 self.current_prompt_line = self.GetCurrentLine()
195 235 self.EnsureCaretVisible()
196 236
197 237
198 238 def scroll_to_bottom(self):
199 239 maxrange = self.GetScrollRange(wx.VERTICAL)
200 240 self.ScrollLines(maxrange)
201 241
202 242
203 243 def pop_completion(self, possibilities, offset=0):
204 244 """ Pops up an autocompletion menu. Offset is the offset
205 245 in characters of the position at which the menu should
206 246 appear, relativ to the cursor.
207 247 """
208 248 self.AutoCompSetIgnoreCase(False)
209 249 self.AutoCompSetAutoHide(False)
210 250 self.AutoCompSetMaxHeight(len(possibilities))
211 251 self.AutoCompShow(offset, " ".join(possibilities))
212 252
213 253
214 254 def get_line_width(self):
215 255 """ Return the width of the line in characters.
216 256 """
217 257 return self.GetSize()[0]/self.GetCharWidth()
218 258
219 259 #--------------------------------------------------------------------------
220 260 # EditWindow API
221 261 #--------------------------------------------------------------------------
222 262
223 263 def OnUpdateUI(self, event):
224 264 """ Override the OnUpdateUI of the EditWindow class, to prevent
225 265 syntax highlighting both for faster redraw, and for more
226 266 consistent look and feel.
227 267 """
228 268
229 269 #--------------------------------------------------------------------------
230 # Private API
270 # Styling API
231 271 #--------------------------------------------------------------------------
232
233 def _apply_style(self):
234 """ Applies the colors for the different text elements and the
235 carret.
236 """
237 self.SetCaretForeground(self.carret_color)
238
239 #self.StyleClearAll()
240 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
241 "fore:#FF0000,back:#0000FF,bold")
242 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
243 "fore:#000000,back:#FF0000,bold")
244
245 for style in self.ANSI_STYLES.values():
246 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
247 272
273 def set_new_style(self):
274 """ call this method with new style and ansi_style to change colors of the console """
275 self._configure_scintilla()
276
277 #--------------------------------------------------------------------------
278 # Private API
279 #--------------------------------------------------------------------------
248 280
249 281 def _configure_scintilla(self):
282 # Marker for complete buffer.
283 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
284 background = self._COMPLETE_BUFFER_BG)
285 # Marker for current input buffer.
286 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
287 background = self._INPUT_BUFFER_BG)
288 # Marker for tracebacks.
289 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
290 background = self._ERROR_BG)
291
250 292 self.SetEOLMode(stc.STC_EOL_LF)
251 293
252 294 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
253 295 # the widget
254 296 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
255 297 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
256 298 # Also allow Ctrl Shift "=" for poor non US keyboard users.
257 299 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
258 300 stc.STC_CMD_ZOOMIN)
259 301
260 302 # Keys: we need to clear some of the keys the that don't play
261 303 # well with a console.
262 304 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
263 305 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
264 306 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
265 307 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
266 308
267 309 self.SetEOLMode(stc.STC_EOL_CRLF)
268 310 self.SetWrapMode(stc.STC_WRAP_CHAR)
269 311 self.SetWrapMode(stc.STC_WRAP_WORD)
270 312 self.SetBufferedDraw(True)
271 313 self.SetUseAntiAliasing(True)
272 314 self.SetLayoutCache(stc.STC_CACHE_PAGE)
273 315 self.SetUndoCollection(False)
274 316 self.SetUseTabs(True)
275 317 self.SetIndent(4)
276 318 self.SetTabWidth(4)
277 319
278 320 # we don't want scintilla's autocompletion to choose
279 321 # automaticaly out of a single choice list, as we pop it up
280 322 # automaticaly
281 323 self.AutoCompSetChooseSingle(False)
282 324 self.AutoCompSetMaxHeight(10)
283 325 # XXX: this doesn't seem to have an effect.
284 326 self.AutoCompSetFillUps('\n')
285 327
286 328 self.SetMargins(3, 3) #text is moved away from border with 3px
287 329 # Suppressing Scintilla margins
288 330 self.SetMarginWidth(0, 0)
289 331 self.SetMarginWidth(1, 0)
290 332 self.SetMarginWidth(2, 0)
291 333
292 self._apply_style()
293
334 #self._apply_style()
335 self.SetCaretForeground(self.carret_color)
336
294 337 # Xterm escape sequences
295 338 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
296 339 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
297 340
298 341 #self.SetEdgeMode(stc.STC_EDGE_LINE)
299 342 #self.SetEdgeColumn(80)
300 343
344
301 345 # styles
302 346 p = self.style
303 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
347
348 if 'default' in p:
349 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
350 else:
351 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "fore:%s,back:%s,size:%d,face:%s"
352 % (self.ANSI_STYLES['0;30'][1], self.background_color,
353 self.faces['size'], self.faces['mono']))
354
355 #all styles = default one
304 356 self.StyleClearAll()
305 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
306 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
307 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
308
309 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
310 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
311 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
312 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
313 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
314 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
315 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
316 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
317 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
318 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
319 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
320 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
321 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
322 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
323
357
358 # XXX: two lines below are usefull if not using the lexer
359 #for style in self.ANSI_STYLES.values():
360 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
361
362 if 'stdout' in p:
363 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
364 if 'stderr' in p:
365 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
366 if 'trace' in p:
367 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
368 if 'bracegood' in p:
369 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
370 if 'bracebad' in p:
371 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
372 if 'comment' in p:
373 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
374 if 'number' in p:
375 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
376 if 'string' in p:
377 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
378 if 'char' in p:
379 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
380 if 'keyword' in p:
381 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
382 if 'keyword' in p:
383 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
384 if 'triple' in p:
385 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
386 if 'tripledouble' in p:
387 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
388 if 'class' in p:
389 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
390 if 'def' in p:
391 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
392 if 'operator' in p:
393 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
394 if 'comment' in p:
395 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
396
324 397 def _on_key_down(self, event, skip=True):
325 398 """ Key press callback used for correcting behavior for
326 399 console-like interfaces: the cursor is constraint to be after
327 400 the last prompt.
328 401
329 402 Return True if event as been catched.
330 403 """
331 404 catched = True
332 405 # Intercept some specific keys.
333 406 if event.KeyCode == ord('L') and event.ControlDown() :
334 407 self.scroll_to_bottom()
335 408 elif event.KeyCode == ord('K') and event.ControlDown() :
336 409 self.input_buffer = ''
337 410 elif event.KeyCode == ord('A') and event.ControlDown() :
338 411 self.GotoPos(self.GetLength())
339 412 self.SetSelectionStart(self.current_prompt_pos)
340 413 self.SetSelectionEnd(self.GetCurrentPos())
341 414 catched = True
342 415 elif event.KeyCode == ord('E') and event.ControlDown() :
343 416 self.GotoPos(self.GetLength())
344 417 catched = True
345 418 elif event.KeyCode == wx.WXK_PAGEUP:
346 419 self.ScrollPages(-1)
347 420 elif event.KeyCode == wx.WXK_PAGEDOWN:
348 421 self.ScrollPages(1)
349 422 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
350 423 self.ScrollLines(-1)
351 424 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
352 425 self.ScrollLines(1)
353 426 else:
354 427 catched = False
355 428
356 429 if self.AutoCompActive():
357 430 event.Skip()
358 431 else:
359 432 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
360 433 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
361 434 catched = True
362 self.CallTipCancel()
363 self.write('\n', refresh=False)
364 # Under windows scintilla seems to be doing funny stuff to the
365 # line returns here, but the getter for input_buffer filters
366 # this out.
367 if sys.platform == 'win32':
368 self.input_buffer = self.input_buffer
369 self._on_enter()
435 if(not self.enter_catched):
436 self.CallTipCancel()
437 self.write('\n', refresh=False)
438 # Under windows scintilla seems to be doing funny stuff to the
439 # line returns here, but the getter for input_buffer filters
440 # this out.
441 if sys.platform == 'win32':
442 self.input_buffer = self.input_buffer
443 self._on_enter()
444 self.enter_catched = True
370 445
371 446 elif event.KeyCode == wx.WXK_HOME:
372 447 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
373 448 self.GotoPos(self.current_prompt_pos)
374 449 catched = True
375 450
376 451 elif event.Modifiers == wx.MOD_SHIFT:
377 452 # FIXME: This behavior is not ideal: if the selection
378 453 # is already started, it will jump.
379 454 self.SetSelectionStart(self.current_prompt_pos)
380 455 self.SetSelectionEnd(self.GetCurrentPos())
381 456 catched = True
382 457
383 458 elif event.KeyCode == wx.WXK_UP:
384 459 if self.GetCurrentLine() > self.current_prompt_line:
385 460 if self.GetCurrentLine() == self.current_prompt_line + 1 \
386 461 and self.GetColumn(self.GetCurrentPos()) < \
387 462 self.GetColumn(self.current_prompt_pos):
388 463 self.GotoPos(self.current_prompt_pos)
389 464 else:
390 465 event.Skip()
391 466 catched = True
392 467
393 468 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
394 469 if self.GetCurrentPos() > self.current_prompt_pos:
395 470 event.Skip()
396 471 catched = True
397 472
398 473 if skip and not catched:
399 474 # Put the cursor back in the edit region
400 475 if self.GetCurrentPos() < self.current_prompt_pos:
401 476 self.GotoPos(self.current_prompt_pos)
402 477 else:
403 478 event.Skip()
404 479
405 480 return catched
406 481
407 482
408 483 def _on_key_up(self, event, skip=True):
409 484 """ If cursor is outside the editing region, put it back.
410 485 """
411 486 event.Skip()
412 487 if self.GetCurrentPos() < self.current_prompt_pos:
413 488 self.GotoPos(self.current_prompt_pos)
414 489
490 self.enter_catched = False #we re-allow enter event processing
415 491
416 492
417 493 if __name__ == '__main__':
418 494 # Some simple code to test the console widget.
419 495 class MainWindow(wx.Frame):
420 496 def __init__(self, parent, id, title):
421 497 wx.Frame.__init__(self, parent, id, title, size=(300,250))
422 498 self._sizer = wx.BoxSizer(wx.VERTICAL)
423 499 self.console_widget = ConsoleWidget(self)
424 500 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
425 501 self.SetSizer(self._sizer)
426 502 self.SetAutoLayout(1)
427 503 self.Show(True)
428 504
429 505 app = wx.PySimpleApp()
430 506 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
431 507 w.SetSize((780, 460))
432 508 w.Show()
433 509
434 510 app.MainLoop()
435 511
436 512
@@ -1,119 +1,175 b''
1 1 """
2 2 Entry point for a simple application giving a graphical frontend to
3 3 ipython.
4 4 """
5 5
6 6 try:
7 7 import wx
8 8 except ImportError, e:
9 9 e.message = """%s
10 10 ________________________________________________________________________________
11 11 You need wxPython to run this application.
12 12 """ % e.message
13 13 e.args = (e.message, ) + e.args[1:]
14 14 raise e
15 15
16 import wx.stc as stc
17
16 18 from wx_frontend import WxController
17 19 import __builtin__
18 20
19 21
20 22 class IPythonXController(WxController):
21 23 """ Sub class of WxController that adds some application-specific
22 24 bindings.
23 25 """
24 26
25 27 debug = False
26 28
27 29 def __init__(self, *args, **kwargs):
30
31 if kwargs['colorset'] == 'black':
32 self.prompt_in1 = \
33 '\n\x01\x1b[0;30m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;30m\x02]: \x01\x1b[0m\x02'
34
35 self.prompt_out = \
36 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
37
28 38 WxController.__init__(self, *args, **kwargs)
29 39 self.ipython0.ask_exit = self.do_exit
40
41 if kwargs['colorset'] == 'black':
42
43 self.carret_color = 'WHITE'
44 self.background_color = 'BLACK'
45
46 self.SetEdgeMode(stc.STC_EDGE_LINE)
47 self.SetEdgeColumn(88)
48
49 self.style = {
50 #'stdout' : '',#fore:#0000FF',
51 #'stderr' : '',#fore:#007f00',
52 #'trace' : '',#fore:#FF0000',
53
54 #'bracegood' : 'fore:#0000FF,back:#0000FF,bold',
55 #'bracebad' : 'fore:#FF0000,back:#0000FF,bold',
56 'default' : "fore:%s,back:%s,size:%d,face:%s,bold"
57 % ("#EEEEEE", self.background_color,
58 self.faces['size'], self.faces['mono']),
59
60 # properties for the various Python lexer styles
61 'comment' : 'fore:#BBBBBB,italic',
62 'number' : 'fore:#FF9692',
63 'string' : 'fore:#ed9d13,italic',
64 'char' : 'fore:#FFFFFF,italic',
65 'keyword' : 'fore:#6AB825,bold',
66 'triple' : 'fore:#FF7BDD',
67 'tripledouble' : 'fore:#FF7BDD',
68 'class' : 'fore:#FF00FF,bold,underline',
69 'def' : 'fore:#FFFF00,bold',
70 'operator' : 'bold'
71 }
72
73 #we define the background of old inputs
74 self._COMPLETE_BUFFER_BG = '#000000' # RRGGBB: Black
75 #we define the background of current input
76 self._INPUT_BUFFER_BG = '#444444' # RRGGBB: Light black
77 #we define the background when an error is reported
78 self._ERROR_BG = '#800000' #'#d22323' #'#AE0021' # RRGGBB: Black
79
80 self.set_new_style()
81
30 82 # Scroll to top
31 83 maxrange = self.GetScrollRange(wx.VERTICAL)
32 84 self.ScrollLines(-maxrange)
33 85
34 86
35 87 def _on_key_down(self, event, skip=True):
36 88 # Intercept Ctrl-D to quit
37 89 if event.KeyCode == ord('D') and event.ControlDown() and \
38 90 self.input_buffer == '' and \
39 91 self._input_state == 'readline':
40 92 wx.CallAfter(self.ask_exit)
41 93 else:
42 94 WxController._on_key_down(self, event, skip=skip)
43 95
44 96
45 97 def ask_exit(self):
46 98 """ Ask the user whether to exit.
47 99 """
48 100 self._input_state = 'subprocess'
49 101 self.write('\n', refresh=False)
50 102 self.capture_output()
51 103 self.ipython0.shell.exit()
52 104 self.release_output()
53 105 if not self.ipython0.exit_now:
54 106 wx.CallAfter(self.new_prompt,
55 107 self.input_prompt_template.substitute(
56 108 number=self.last_result['number'] + 1))
57 109 else:
58 110 wx.CallAfter(wx.GetApp().Exit)
59 111 self.write('Exiting ...', refresh=False)
60 112
61 113
62 114 def do_exit(self):
63 115 """ Exits the interpreter, kills the windows.
64 116 """
65 117 WxController.do_exit(self)
66 118 self.release_output()
67 119 wx.CallAfter(wx.Exit)
68 120
69 121
70 122
71 123 class IPythonX(wx.Frame):
72 124 """ Main frame of the IPythonX app.
73 125 """
74 126
75 def __init__(self, parent, id, title, debug=False):
127 def __init__(self, parent, id, title, debug=False, colorset='white'):
76 128 wx.Frame.__init__(self, parent, id, title, size=(300,250))
77 129 self._sizer = wx.BoxSizer(wx.VERTICAL)
78 self.shell = IPythonXController(self, debug=debug)
130 self.shell = IPythonXController(self, debug=debug, colorset=colorset)
79 131 self._sizer.Add(self.shell, 1, wx.EXPAND)
80 132 self.SetSizer(self._sizer)
81 133 self.SetAutoLayout(1)
82 134 self.Show(True)
83 135 wx.EVT_CLOSE(self, self.on_close)
84 136
85 137
86 138 def on_close(self, event):
87 139 """ Called on closing the windows.
88 140
89 141 Stops the event loop, to close all the child windows.
90 142 """
91 143 wx.CallAfter(wx.Exit)
92 144
93 145
94 146 def main():
95 147 from optparse import OptionParser
96 148 usage = """usage: %prog [options]
97 149
98 150 Simple graphical frontend to IPython, using WxWidgets."""
99 151 parser = OptionParser(usage=usage)
100 152 parser.add_option("-d", "--debug",
101 153 action="store_true", dest="debug", default=False,
102 154 help="Enable debug message for the wx frontend.")
103 155
156 parser.add_option("-s", "--style",
157 dest="colorset", default="white",
158 help="style: white, black")
159
104 160 options, args = parser.parse_args()
105 161
106 162 # Clear the options, to avoid having the ipython0 instance complain
107 163 import sys
108 164 sys.argv = sys.argv[:1]
109 165
110 166 app = wx.PySimpleApp()
111 frame = IPythonX(None, wx.ID_ANY, 'IPythonX', debug=options.debug)
167 frame = IPythonX(None, wx.ID_ANY, 'IPythonX', debug=options.debug, colorset=options.colorset)
112 168 frame.shell.SetFocus()
113 169 frame.shell.app = app
114 170 frame.SetSize((680, 460))
115 171
116 172 app.MainLoop()
117 173
118 174 if __name__ == '__main__':
119 175 main()
@@ -1,558 +1,546 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 This class inherits from ConsoleWidget, that provides a console-like
9 9 widget to provide a text-rendering widget suitable for a terminal.
10 10 """
11 11
12 12 __docformat__ = "restructuredtext en"
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-------------------------------------------------------------------------------
20 20
21 21 #-------------------------------------------------------------------------------
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24
25 25 # Major library imports
26 26 import re
27 27 import __builtin__
28 28 import sys
29 29 from threading import Lock
30 30 import string
31 31
32 32 import wx
33 33 from wx import stc
34 34
35 35 # Ipython-specific imports.
36 36 from IPython.frontend._process import PipedProcess
37 37 from console_widget import ConsoleWidget
38 38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39 39
40 40 #-------------------------------------------------------------------------------
41 41 # Constants
42 42 #-------------------------------------------------------------------------------
43 43
44 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
45 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
46 _ERROR_BG = '#FFF1F1' # Nice red
47
48 44 _COMPLETE_BUFFER_MARKER = 31
49 45 _ERROR_MARKER = 30
50 46 _INPUT_MARKER = 29
51 47
52 prompt_in1 = \
53 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
54
55 prompt_out = \
56 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
57
58 48 #-------------------------------------------------------------------------------
59 49 # Classes to implement the Wx frontend
60 50 #-------------------------------------------------------------------------------
61 51 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 52 """Classes to provide a Wx frontend to the
63 53 IPython.kernel.core.interpreter.
64 54
65 55 This class inherits from ConsoleWidget, that provides a console-like
66 56 widget to provide a text-rendering widget suitable for a terminal.
67 57 """
58 prompt_in1 = \
59 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
68 60
69 output_prompt_template = string.Template(prompt_out)
70
71 input_prompt_template = string.Template(prompt_in1)
61 prompt_out = \
62 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
72 63
73 64 # Print debug info on what is happening to the console.
74 65 debug = False
75 66
76 67 # The title of the terminal, as captured through the ANSI escape
77 68 # sequences.
78 69 def _set_title(self, title):
79 70 return self.Parent.SetTitle(title)
80 71
81 72 def _get_title(self):
82 73 return self.Parent.GetTitle()
83 74
84 75 title = property(_get_title, _set_title)
85 76
86 77
87 78 # The buffer being edited.
88 79 # We are duplicating the definition here because of multiple
89 80 # inheritence
90 81 def _set_input_buffer(self, string):
91 82 ConsoleWidget._set_input_buffer(self, string)
92 83 self._colorize_input_buffer()
93 84
94 85 def _get_input_buffer(self):
95 86 """ Returns the text in current edit buffer.
96 87 """
97 88 return ConsoleWidget._get_input_buffer(self)
98 89
99 90 input_buffer = property(_get_input_buffer, _set_input_buffer)
100 91
101 92
102 93 #--------------------------------------------------------------------------
103 94 # Private Attributes
104 95 #--------------------------------------------------------------------------
105 96
106 97 # A flag governing the behavior of the input. Can be:
107 98 #
108 99 # 'readline' for readline-like behavior with a prompt
109 100 # and an edit buffer.
110 101 # 'raw_input' similar to readline, but triggered by a raw-input
111 102 # call. Can be used by subclasses to act differently.
112 103 # 'subprocess' for sending the raw input directly to a
113 104 # subprocess.
114 105 # 'buffering' for buffering of the input, that will be used
115 106 # when the input state switches back to another state.
116 107 _input_state = 'readline'
117 108
118 109 # Attribute to store reference to the pipes of a subprocess, if we
119 110 # are running any.
120 111 _running_process = False
121 112
122 113 # A queue for writing fast streams to the screen without flooding the
123 114 # event loop
124 115 _out_buffer = []
125 116
126 117 # A lock to lock the _out_buffer to make sure we don't empty it
127 118 # while it is being swapped
128 119 _out_buffer_lock = Lock()
129 120
130 121 # The different line markers used to higlight the prompts.
131 122 _markers = dict()
132 123
133 124 #--------------------------------------------------------------------------
134 125 # Public API
135 126 #--------------------------------------------------------------------------
136 127
137 128 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 129 size=wx.DefaultSize,
139 130 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
140 131 *args, **kwds):
141 132 """ Create Shell instance.
142 133 """
134 self.load_prompt()
135
143 136 ConsoleWidget.__init__(self, parent, id, pos, size, style)
144 137 PrefilterFrontEnd.__init__(self, **kwds)
145 138
146 139 # Stick in our own raw_input:
147 140 self.ipython0.raw_input = self.raw_input
148 141
149 # Marker for complete buffer.
150 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
151 background=_COMPLETE_BUFFER_BG)
152 # Marker for current input buffer.
153 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
154 background=_INPUT_BUFFER_BG)
155 # Marker for tracebacks.
156 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
157 background=_ERROR_BG)
158
159 142 # A time for flushing the write buffer
160 143 BUFFER_FLUSH_TIMER_ID = 100
161 144 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
162 145 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
163 146
164 147 if 'debug' in kwds:
165 148 self.debug = kwds['debug']
166 149 kwds.pop('debug')
167 150
168 151 # Inject self in namespace, for debug
169 152 if self.debug:
170 153 self.shell.user_ns['self'] = self
171 154 # Inject our own raw_input in namespace
172 155 self.shell.user_ns['raw_input'] = self.raw_input
173 156
157 def load_prompt(self):
158 self.output_prompt_template = string.Template(self.prompt_out)
174 159
160 self.input_prompt_template = string.Template(self.prompt_in1)
161
162
175 163 def raw_input(self, prompt=''):
176 164 """ A replacement from python's raw_input.
177 165 """
178 166 self.new_prompt(prompt)
179 167 self._input_state = 'raw_input'
180 168 if hasattr(self, '_cursor'):
181 169 del self._cursor
182 170 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
183 171 self.__old_on_enter = self._on_enter
184 172 event_loop = wx.EventLoop()
185 173 def my_on_enter():
186 174 event_loop.Exit()
187 175 self._on_enter = my_on_enter
188 176 # XXX: Running a separate event_loop. Ugly.
189 177 event_loop.Run()
190 178 self._on_enter = self.__old_on_enter
191 179 self._input_state = 'buffering'
192 180 self._cursor = wx.BusyCursor()
193 181 return self.input_buffer.rstrip('\n')
194 182
195 183
196 184 def system_call(self, command_string):
197 185 self._input_state = 'subprocess'
198 186 event_loop = wx.EventLoop()
199 187 def _end_system_call():
200 188 self._input_state = 'buffering'
201 189 self._running_process = False
202 190 event_loop.Exit()
203 191
204 192 self._running_process = PipedProcess(command_string,
205 193 out_callback=self.buffered_write,
206 194 end_callback = _end_system_call)
207 195 self._running_process.start()
208 196 # XXX: Running a separate event_loop. Ugly.
209 197 event_loop.Run()
210 198 # Be sure to flush the buffer.
211 199 self._buffer_flush(event=None)
212 200
213 201
214 202 def do_calltip(self):
215 203 """ Analyse current and displays useful calltip for it.
216 204 """
217 205 if self.debug:
218 206 print >>sys.__stdout__, "do_calltip"
219 207 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
220 208 symbol = self.input_buffer
221 209 symbol_string = separators.split(symbol)[-1]
222 210 base_symbol_string = symbol_string.split('.')[0]
223 211 if base_symbol_string in self.shell.user_ns:
224 212 symbol = self.shell.user_ns[base_symbol_string]
225 213 elif base_symbol_string in self.shell.user_global_ns:
226 214 symbol = self.shell.user_global_ns[base_symbol_string]
227 215 elif base_symbol_string in __builtin__.__dict__:
228 216 symbol = __builtin__.__dict__[base_symbol_string]
229 217 else:
230 218 return False
231 219 try:
232 220 for name in symbol_string.split('.')[1:] + ['__doc__']:
233 221 symbol = getattr(symbol, name)
234 222 self.AutoCompCancel()
235 223 # Check that the symbol can indeed be converted to a string:
236 224 symbol += ''
237 225 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
238 226 except:
239 227 # The retrieve symbol couldn't be converted to a string
240 228 pass
241 229
242 230
243 231 def _popup_completion(self, create=False):
244 232 """ Updates the popup completion menu if it exists. If create is
245 233 true, open the menu.
246 234 """
247 235 if self.debug:
248 236 print >>sys.__stdout__, "_popup_completion"
249 237 line = self.input_buffer
250 238 if (self.AutoCompActive() and line and not line[-1] == '.') \
251 239 or create==True:
252 240 suggestion, completions = self.complete(line)
253 241 offset=0
254 242 if completions:
255 243 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
256 244 residual = complete_sep.split(line)[-1]
257 245 offset = len(residual)
258 246 self.pop_completion(completions, offset=offset)
259 247 if self.debug:
260 248 print >>sys.__stdout__, completions
261 249
262 250
263 251 def buffered_write(self, text):
264 252 """ A write method for streams, that caches the stream in order
265 253 to avoid flooding the event loop.
266 254
267 255 This can be called outside of the main loop, in separate
268 256 threads.
269 257 """
270 258 self._out_buffer_lock.acquire()
271 259 self._out_buffer.append(text)
272 260 self._out_buffer_lock.release()
273 261 if not self._buffer_flush_timer.IsRunning():
274 262 wx.CallAfter(self._buffer_flush_timer.Start,
275 263 milliseconds=100, oneShot=True)
276 264
277 265
278 266 #--------------------------------------------------------------------------
279 267 # LineFrontEnd interface
280 268 #--------------------------------------------------------------------------
281 269
282 270 def execute(self, python_string, raw_string=None):
283 271 self._input_state = 'buffering'
284 272 self.CallTipCancel()
285 273 self._cursor = wx.BusyCursor()
286 274 if raw_string is None:
287 275 raw_string = python_string
288 276 end_line = self.current_prompt_line \
289 277 + max(1, len(raw_string.split('\n'))-1)
290 278 for i in range(self.current_prompt_line, end_line):
291 279 if i in self._markers:
292 280 self.MarkerDeleteHandle(self._markers[i])
293 281 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
294 282 # Use a callafter to update the display robustly under windows
295 283 def callback():
296 284 self.GotoPos(self.GetLength())
297 285 PrefilterFrontEnd.execute(self, python_string,
298 286 raw_string=raw_string)
299 287 wx.CallAfter(callback)
300 288
301 289
302 290 def execute_command(self, command, hidden=False):
303 291 """ Execute a command, not only in the model, but also in the
304 292 view.
305 293 """
306 294 if hidden:
307 295 return self.shell.execute(command)
308 296 else:
309 297 # XXX: we are not storing the input buffer previous to the
310 298 # execution, as this forces us to run the execution
311 299 # input_buffer a yield, which is not good.
312 300 ##current_buffer = self.shell.control.input_buffer
313 301 command = command.rstrip()
314 302 if len(command.split('\n')) > 1:
315 303 # The input command is several lines long, we need to
316 304 # force the execution to happen
317 305 command += '\n'
318 306 cleaned_command = self.prefilter_input(command)
319 307 self.input_buffer = command
320 308 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
321 309 # recursive yields.
322 310 self.ProcessEvent(wx.PaintEvent())
323 311 self.write('\n')
324 312 if not self.is_complete(cleaned_command + '\n'):
325 313 self._colorize_input_buffer()
326 314 self.render_error('Incomplete or invalid input')
327 315 self.new_prompt(self.input_prompt_template.substitute(
328 316 number=(self.last_result['number'] + 1)))
329 317 return False
330 318 self._on_enter()
331 319 return True
332 320
333 321
334 322 def save_output_hooks(self):
335 323 self.__old_raw_input = __builtin__.raw_input
336 324 PrefilterFrontEnd.save_output_hooks(self)
337 325
338 326 def capture_output(self):
339 327 self.SetLexer(stc.STC_LEX_NULL)
340 328 PrefilterFrontEnd.capture_output(self)
341 329 __builtin__.raw_input = self.raw_input
342 330
343 331
344 332 def release_output(self):
345 333 __builtin__.raw_input = self.__old_raw_input
346 334 PrefilterFrontEnd.release_output(self)
347 335 self.SetLexer(stc.STC_LEX_PYTHON)
348 336
349 337
350 338 def after_execute(self):
351 339 PrefilterFrontEnd.after_execute(self)
352 340 # Clear the wait cursor
353 341 if hasattr(self, '_cursor'):
354 342 del self._cursor
355 343 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
356 344
357 345
358 346 def show_traceback(self):
359 347 start_line = self.GetCurrentLine()
360 348 PrefilterFrontEnd.show_traceback(self)
361 349 self.ProcessEvent(wx.PaintEvent())
362 350 #wx.Yield()
363 351 for i in range(start_line, self.GetCurrentLine()):
364 352 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
365 353
366 354
367 355 #--------------------------------------------------------------------------
368 356 # FrontEndBase interface
369 357 #--------------------------------------------------------------------------
370 358
371 359 def render_error(self, e):
372 360 start_line = self.GetCurrentLine()
373 361 self.write('\n' + e + '\n')
374 362 for i in range(start_line, self.GetCurrentLine()):
375 363 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
376 364
377 365
378 366 #--------------------------------------------------------------------------
379 367 # ConsoleWidget interface
380 368 #--------------------------------------------------------------------------
381 369
382 370 def new_prompt(self, prompt):
383 371 """ Display a new prompt, and start a new input buffer.
384 372 """
385 373 self._input_state = 'readline'
386 374 ConsoleWidget.new_prompt(self, prompt)
387 375 i = self.current_prompt_line
388 376 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
389 377
390 378
391 379 def write(self, *args, **kwargs):
392 380 # Avoid multiple inheritence, be explicit about which
393 381 # parent method class gets called
394 382 ConsoleWidget.write(self, *args, **kwargs)
395 383
396 384
397 385 def _on_key_down(self, event, skip=True):
398 386 """ Capture the character events, let the parent
399 387 widget handle them, and put our logic afterward.
400 388 """
401 389 # FIXME: This method needs to be broken down in smaller ones.
402 390 current_line_number = self.GetCurrentLine()
403 391 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
404 392 # Capture Control-C
405 393 if self._input_state == 'subprocess':
406 394 if self.debug:
407 395 print >>sys.__stderr__, 'Killing running process'
408 396 if hasattr(self._running_process, 'process'):
409 397 self._running_process.process.kill()
410 398 elif self._input_state == 'buffering':
411 399 if self.debug:
412 400 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
413 401 raise KeyboardInterrupt
414 402 # XXX: We need to make really sure we
415 403 # get back to a prompt.
416 404 elif self._input_state == 'subprocess' and (
417 405 ( event.KeyCode<256 and
418 406 not event.ControlDown() )
419 407 or
420 408 ( event.KeyCode in (ord('d'), ord('D')) and
421 409 event.ControlDown())):
422 410 # We are running a process, we redirect keys.
423 411 ConsoleWidget._on_key_down(self, event, skip=skip)
424 412 char = chr(event.KeyCode)
425 413 # Deal with some inconsistency in wx keycodes:
426 414 if char == '\r':
427 415 char = '\n'
428 416 elif not event.ShiftDown():
429 417 char = char.lower()
430 418 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
431 419 char = '\04'
432 420 self._running_process.process.stdin.write(char)
433 421 self._running_process.process.stdin.flush()
434 422 elif event.KeyCode in (ord('('), 57, 53):
435 423 # Calltips
436 424 event.Skip()
437 425 self.do_calltip()
438 426 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
439 427 event.Skip()
440 428 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
441 429 wx.CallAfter(self._popup_completion, create=True)
442 430 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
443 431 wx.WXK_RIGHT, wx.WXK_ESCAPE):
444 432 wx.CallAfter(self._popup_completion)
445 433 else:
446 434 # Up history
447 435 if event.KeyCode == wx.WXK_UP and (
448 436 ( current_line_number == self.current_prompt_line and
449 437 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
450 438 or event.ControlDown() ):
451 439 new_buffer = self.get_history_previous(
452 440 self.input_buffer)
453 441 if new_buffer is not None:
454 442 self.input_buffer = new_buffer
455 443 if self.GetCurrentLine() > self.current_prompt_line:
456 444 # Go to first line, for seemless history up.
457 445 self.GotoPos(self.current_prompt_pos)
458 446 # Down history
459 447 elif event.KeyCode == wx.WXK_DOWN and (
460 448 ( current_line_number == self.LineCount -1 and
461 449 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
462 450 or event.ControlDown() ):
463 451 new_buffer = self.get_history_next()
464 452 if new_buffer is not None:
465 453 self.input_buffer = new_buffer
466 454 # Tab-completion
467 455 elif event.KeyCode == ord('\t'):
468 456 current_line, current_line_number = self.CurLine
469 457 if not re.match(r'^\s*$', current_line):
470 458 self.complete_current_input()
471 459 if self.AutoCompActive():
472 460 wx.CallAfter(self._popup_completion, create=True)
473 461 else:
474 462 event.Skip()
475 463 else:
476 464 ConsoleWidget._on_key_down(self, event, skip=skip)
477 465
478 466
479 467 def _on_key_up(self, event, skip=True):
480 468 """ Called when any key is released.
481 469 """
482 470 if event.KeyCode in (59, ord('.')):
483 471 # Intercepting '.'
484 472 event.Skip()
485 473 wx.CallAfter(self._popup_completion, create=True)
486 474 else:
487 475 ConsoleWidget._on_key_up(self, event, skip=skip)
488 476
489 477
490 478 def _on_enter(self):
491 479 """ Called on return key down, in readline input_state.
492 480 """
493 481 if self.debug:
494 482 print >>sys.__stdout__, repr(self.input_buffer)
495 483 PrefilterFrontEnd._on_enter(self)
496 484
497 485
498 486 #--------------------------------------------------------------------------
499 487 # EditWindow API
500 488 #--------------------------------------------------------------------------
501 489
502 490 def OnUpdateUI(self, event):
503 491 """ Override the OnUpdateUI of the EditWindow class, to prevent
504 492 syntax highlighting both for faster redraw, and for more
505 493 consistent look and feel.
506 494 """
507 495 if not self._input_state == 'readline':
508 496 ConsoleWidget.OnUpdateUI(self, event)
509 497
510 498 #--------------------------------------------------------------------------
511 499 # Private API
512 500 #--------------------------------------------------------------------------
513 501
514 502 def _buffer_flush(self, event):
515 503 """ Called by the timer to flush the write buffer.
516 504
517 505 This is always called in the mainloop, by the wx timer.
518 506 """
519 507 self._out_buffer_lock.acquire()
520 508 _out_buffer = self._out_buffer
521 509 self._out_buffer = []
522 510 self._out_buffer_lock.release()
523 511 self.write(''.join(_out_buffer), refresh=False)
524 512
525 513
526 514 def _colorize_input_buffer(self):
527 515 """ Keep the input buffer lines at a bright color.
528 516 """
529 517 if not self._input_state in ('readline', 'raw_input'):
530 518 return
531 519 end_line = self.GetCurrentLine()
532 520 if not sys.platform == 'win32':
533 521 end_line += 1
534 522 for i in range(self.current_prompt_line, end_line):
535 523 if i in self._markers:
536 524 self.MarkerDeleteHandle(self._markers[i])
537 525 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
538 526
539 527
540 528 if __name__ == '__main__':
541 529 class MainWindow(wx.Frame):
542 530 def __init__(self, parent, id, title):
543 531 wx.Frame.__init__(self, parent, id, title, size=(300,250))
544 532 self._sizer = wx.BoxSizer(wx.VERTICAL)
545 533 self.shell = WxController(self)
546 534 self._sizer.Add(self.shell, 1, wx.EXPAND)
547 535 self.SetSizer(self._sizer)
548 536 self.SetAutoLayout(1)
549 537 self.Show(True)
550 538
551 539 app = wx.PySimpleApp()
552 540 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
553 541 frame.shell.SetFocus()
554 542 frame.SetSize((680, 460))
555 543 self = frame.shell
556 544
557 545 app.MainLoop()
558 546
General Comments 0
You need to be logged in to leave comments. Login now