##// END OF EJS Templates
Isolate the displayhook created by ipython0. This fixes a test not...
Gael Varoquaux -
Show More
@@ -1,192 +1,203 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
28 28 from IPython.ipmaker import make_IPython
29 29 from IPython.ipapi import IPApi
30 30 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
31 31
32 32 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
33 33
34 34 from IPython.genutils import Term
35 35 import pydoc
36 36 import os
37 37
38 38
39 39 def mk_system_call(system_call_function, command):
40 40 """ given a os.system replacement, and a leading string command,
41 41 returns a function that will execute the command with the given
42 42 argument string.
43 43 """
44 44 def my_system_call(args):
45 45 system_call_function("%s %s" % (command, args))
46 46 return my_system_call
47 47
48 48 #-------------------------------------------------------------------------------
49 49 # Frontend class using ipython0 to do the prefiltering.
50 50 #-------------------------------------------------------------------------------
51 51 class PrefilterFrontEnd(LineFrontEndBase):
52 52 """ Class that uses ipython0 to do prefilter the input, do the
53 53 completion and the magics.
54 54
55 55 The core trick is to use an ipython0 instance to prefilter the
56 56 input, and share the namespace between the interpreter instance used
57 57 to execute the statements and the ipython0 used for code
58 58 completion...
59 59 """
60 60
61 61 def __init__(self, *args, **kwargs):
62 62 LineFrontEndBase.__init__(self, *args, **kwargs)
63 self.save_output_hooks()
63 64 # Instanciate an IPython0 interpreter to be able to use the
64 65 # prefiltering.
65 66 self.ipython0 = make_IPython()
66 67 # Set the pager:
67 68 self.ipython0.set_hook('show_in_pager',
68 69 lambda s, string: self.write("\n"+string))
69 70 self.ipython0.write = self.write
70 71 self._ip = _ip = IPApi(self.ipython0)
71 72 # XXX: Hack: mix the two namespaces
72 73 self.shell.user_ns = self.ipython0.user_ns
73 74 self.shell.user_global_ns = self.ipython0.user_global_ns
74 75 # Make sure the raw system call doesn't get called, as we don't
75 76 # have a stdin accessible.
76 77 self._ip.system = self.system_call
77 78 # XXX: Muck around with magics so that they work better
78 79 # in our environment
79 80 self.ipython0.magic_ls = mk_system_call(self.system_call,
80 81 'ls -CF')
82 # And now clean up the mess created by ipython0
83 self.release_output()
81 84 self.shell.output_trap = RedirectorOutputTrap(
82 85 out_callback=self.write,
83 86 err_callback=self.write,
84 87 )
85 88 self.shell.traceback_trap = SyncTracebackTrap(
86 89 formatters=self.shell.traceback_trap.formatters,
87 90 )
88 # Capture and release the outputs, to make sure all the
89 # shadow variables are set
90 self.capture_output()
91 self.release_output()
92 91
93 92 #--------------------------------------------------------------------------
94 93 # FrontEndBase interface
95 94 #--------------------------------------------------------------------------
96 95
97 96 def show_traceback(self):
98 97 """ Use ipython0 to capture the last traceback and display it.
99 98 """
100 99 self.capture_output()
101 100 self.ipython0.showtraceback()
102 101 self.release_output()
103 102
104 103
105 104 def execute(self, python_string, raw_string=None):
106 105 self.capture_output()
107 106 LineFrontEndBase.execute(self, python_string,
108 107 raw_string=raw_string)
109 108 self.release_output()
110 109
111 110
111 def save_output_hooks(self):
112 """ Store all the output hooks we can think of, to be able to
113 restore them.
114
115 We need to do this early, as starting the ipython0 instance will
116 screw ouput hooks.
117 """
118 self.__old_cout_write = Term.cout.write
119 self.__old_cerr_write = Term.cerr.write
120 self.__old_stdout = sys.stdout
121 self.__old_stderr= sys.stderr
122 self.__old_help_output = pydoc.help.output
123 self.__old_display_hook = sys.displayhook
124
125
112 126 def capture_output(self):
113 127 """ Capture all the output mechanisms we can think of.
114 128 """
115 self.__old_cout_write = Term.cout.write
116 self.__old_err_write = Term.cerr.write
129 self.save_output_hooks()
117 130 Term.cout.write = self.write
118 131 Term.cerr.write = self.write
119 self.__old_stdout = sys.stdout
120 self.__old_stderr= sys.stderr
121 132 sys.stdout = Term.cout
122 133 sys.stderr = Term.cerr
123 self.__old_help_output = pydoc.help.output
124 134 pydoc.help.output = self.shell.output_trap.out
125 135
126 136
127 137 def release_output(self):
128 138 """ Release all the different captures we have made.
129 139 """
130 140 Term.cout.write = self.__old_cout_write
131 Term.cerr.write = self.__old_err_write
141 Term.cerr.write = self.__old_cerr_write
132 142 sys.stdout = self.__old_stdout
133 143 sys.stderr = self.__old_stderr
134 144 pydoc.help.output = self.__old_help_output
145 sys.displayhook = self.__old_display_hook
135 146
136 147
137 148 def complete(self, line):
138 149 word = line.split('\n')[-1].split(' ')[-1]
139 150 completions = self.ipython0.complete(word)
140 151 # FIXME: The proper sort should be done in the complete method.
141 152 key = lambda x: x.replace('_', '')
142 153 completions.sort(key=key)
143 154 if completions:
144 155 prefix = common_prefix(completions)
145 156 line = line[:-len(word)] + prefix
146 157 return line, completions
147 158
148 159
149 160 #--------------------------------------------------------------------------
150 161 # LineFrontEndBase interface
151 162 #--------------------------------------------------------------------------
152 163
153 164 def prefilter_input(self, input_string):
154 165 """ Using IPython0 to prefilter the commands to turn them
155 166 in executable statements that are valid Python strings.
156 167 """
157 168 input_string = LineFrontEndBase.prefilter_input(self, input_string)
158 169 filtered_lines = []
159 170 # The IPython0 prefilters sometime produce output. We need to
160 171 # capture it.
161 172 self.capture_output()
162 173 self.last_result = dict(number=self.prompt_number)
163 174 try:
164 175 for line in input_string.split('\n'):
165 176 filtered_lines.append(self.ipython0.prefilter(line, False))
166 177 except:
167 178 # XXX: probably not the right thing to do.
168 179 self.ipython0.showsyntaxerror()
169 180 self.after_execute()
170 181 finally:
171 182 self.release_output()
172 183
173 184 filtered_string = '\n'.join(filtered_lines)
174 185 return filtered_string
175 186
176 187
177 188 #--------------------------------------------------------------------------
178 189 # PrefilterFrontEnd interface
179 190 #--------------------------------------------------------------------------
180 191
181 192 def system_call(self, command_string):
182 193 """ Allows for frontend to define their own system call, to be
183 194 able capture output and redirect input.
184 195 """
185 196 return os.system(command_string)
186 197
187 198
188 199 def do_exit(self):
189 200 """ Exit the shell, cleanup and save the history.
190 201 """
191 202 self.ipython0.atexit_operations()
192 203
@@ -1,129 +1,130 b''
1 1 # encoding: utf-8
2 2 """
3 3 Test process execution and IO redirection.
4 4 """
5 5
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is
12 12 # in the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
16 16 from cStringIO import StringIO
17 17 import string
18 import sys
18 19
19 20 class TestPrefilterFrontEnd(PrefilterFrontEnd):
20 21
21 22 input_prompt_template = string.Template('')
22 23 output_prompt_template = string.Template('')
23 24
24 25 def __init__(self):
25 26 self.out = StringIO()
26 27 PrefilterFrontEnd.__init__(self)
27 28
28 29 def write(self, string):
29 30 self.out.write(string)
30 31
31 32 def _on_enter(self):
32 33 self.input_buffer += '\n'
33 34 PrefilterFrontEnd._on_enter(self)
34 35
35 36
36 37 def test_execution():
37 38 """ Test execution of a command.
38 39 """
39 40 f = TestPrefilterFrontEnd()
40 41 f.input_buffer = 'print 1\n'
41 42 f._on_enter()
42 43 assert f.out.getvalue() == '1\n'
43 44
44 45
45 46 def test_multiline():
46 47 """ Test execution of a multiline command.
47 48 """
48 49 f = TestPrefilterFrontEnd()
49 50 f.input_buffer = 'if True:'
50 51 f._on_enter()
51 52 f.input_buffer += 'print 1'
52 53 f._on_enter()
53 54 assert f.out.getvalue() == ''
54 55 f._on_enter()
55 56 assert f.out.getvalue() == '1\n'
56 57 f = TestPrefilterFrontEnd()
57 58 f.input_buffer='(1 +'
58 59 f._on_enter()
59 60 f.input_buffer += '0)'
60 61 f._on_enter()
61 62 assert f.out.getvalue() == ''
62 63 f._on_enter()
63 64 assert f.out.getvalue() == '1\n'
64 65
65 66
66 67 def test_capture():
67 68 """ Test the capture of output in different channels.
68 69 """
69 70 # Test on the OS-level stdout, stderr.
70 71 f = TestPrefilterFrontEnd()
71 72 f.input_buffer = \
72 73 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
73 74 f._on_enter()
74 75 assert f.out.getvalue() == '1'
75 76 f = TestPrefilterFrontEnd()
76 77 f.input_buffer = \
77 78 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
78 79 f._on_enter()
79 80 assert f.out.getvalue() == '1'
80 81
81 82
82 83 def test_magic():
83 84 """ Test the magic expansion and history.
84 85
85 86 This test is fairly fragile and will break when magics change.
86 87 """
87 88 f = TestPrefilterFrontEnd()
88 89 f.input_buffer += '%who\n'
89 90 f._on_enter()
90 91 assert f.out.getvalue() == 'Interactive namespace is empty.\n'
91 92
92 93
93 94 def test_help():
94 95 """ Test object inspection.
95 96 """
96 97 f = TestPrefilterFrontEnd()
97 98 f.input_buffer += "def f():"
98 99 f._on_enter()
99 100 f.input_buffer += "'foobar'"
100 101 f._on_enter()
101 102 f.input_buffer += "pass"
102 103 f._on_enter()
103 104 f._on_enter()
104 105 f.input_buffer += "f?"
105 106 f._on_enter()
106 107 assert f.out.getvalue().split()[-1] == 'foobar'
107 108
108 109
109 110 def test_completion():
110 111 """ Test command-line completion.
111 112 """
112 113 f = TestPrefilterFrontEnd()
113 114 f.input_buffer = 'zzza = 1'
114 115 f._on_enter()
115 116 f.input_buffer = 'zzzb = 2'
116 117 f._on_enter()
117 118 f.input_buffer = 'zz'
118 119 f.complete_current_input()
119 120 assert f.out.getvalue() == '\nzzza zzzb '
120 121 assert f.input_buffer == 'zzz'
121 122
122 123
123 124 if __name__ == '__main__':
124 125 test_magic()
125 126 test_help()
126 127 test_execution()
127 128 test_multiline()
128 129 test_capture()
129 130 test_completion()
@@ -1,415 +1,413 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 sys
27 27 LINESEP = '\n'
28 28 if sys.platform == 'win32':
29 29 LINESEP = '\n\r'
30 30
31 31 import re
32 32
33 33 # FIXME: Need to provide an API for non user-generated display on the
34 34 # screen: this should not be editable by the user.
35 35
36 36 _DEFAULT_SIZE = 10
37 37
38 38 _DEFAULT_STYLE = {
39 39 'stdout' : 'fore:#0000FF',
40 40 'stderr' : 'fore:#007f00',
41 41 'trace' : 'fore:#FF0000',
42 42
43 43 'default' : 'size:%d' % _DEFAULT_SIZE,
44 44 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
45 45 'bracebad' : 'fore:#000000,back:#FF0000,bold',
46 46
47 47 # properties for the various Python lexer styles
48 48 'comment' : 'fore:#007F00',
49 49 'number' : 'fore:#007F7F',
50 50 'string' : 'fore:#7F007F,italic',
51 51 'char' : 'fore:#7F007F,italic',
52 52 'keyword' : 'fore:#00007F,bold',
53 53 'triple' : 'fore:#7F0000',
54 54 'tripledouble' : 'fore:#7F0000',
55 55 'class' : 'fore:#0000FF,bold,underline',
56 56 'def' : 'fore:#007F7F,bold',
57 57 'operator' : 'bold'
58 58 }
59 59
60 60 # new style numbers
61 61 _STDOUT_STYLE = 15
62 62 _STDERR_STYLE = 16
63 63 _TRACE_STYLE = 17
64 64
65 65
66 66 # system colors
67 67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
68 68
69 69 #-------------------------------------------------------------------------------
70 70 # The console widget class
71 71 #-------------------------------------------------------------------------------
72 72 class ConsoleWidget(editwindow.EditWindow):
73 73 """ Specialized styled text control view for console-like workflow.
74 74
75 75 This widget is mainly interested in dealing with the prompt and
76 76 keeping the cursor inside the editing line.
77 77 """
78 78
79 79 # This is where the title captured from the ANSI escape sequences are
80 80 # stored.
81 81 title = 'Console'
82 82
83 83 # The buffer being edited.
84 84 def _set_input_buffer(self, string):
85 85 self.SetSelection(self.current_prompt_pos, self.GetLength())
86 86 self.ReplaceSelection(string)
87 87 self.GotoPos(self.GetLength())
88 88
89 89 def _get_input_buffer(self):
90 90 """ Returns the text in current edit buffer.
91 91 """
92 92 input_buffer = self.GetTextRange(self.current_prompt_pos,
93 93 self.GetLength())
94 94 input_buffer = input_buffer.replace(LINESEP, '\n')
95 95 return input_buffer
96 96
97 97 input_buffer = property(_get_input_buffer, _set_input_buffer)
98 98
99 99 style = _DEFAULT_STYLE.copy()
100 100
101 101 # Translation table from ANSI escape sequences to color. Override
102 102 # this to specify your colors.
103 103 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
104 104 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
105 105 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
106 106 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
107 107 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
108 108 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
109 109 '1;34': [12, 'LIGHT BLUE'], '1;35':
110 110 [13, 'MEDIUM VIOLET RED'],
111 111 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
112 112
113 113 # The color of the carret (call _apply_style() after setting)
114 114 carret_color = 'BLACK'
115 115
116 116 #--------------------------------------------------------------------------
117 117 # Public API
118 118 #--------------------------------------------------------------------------
119 119
120 120 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
121 121 size=wx.DefaultSize, style=0, ):
122 122 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
123 123 self._configure_scintilla()
124 124
125 125 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
126 126 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
127 127
128 128
129 129 def write(self, text, refresh=True):
130 130 """ Write given text to buffer, while translating the ansi escape
131 131 sequences.
132 132 """
133 133 # XXX: do not put print statements to sys.stdout/sys.stderr in
134 134 # this method, the print statements will call this method, as
135 135 # you will end up with an infinit loop
136 if self.debug:
137 print >>sys.__stderr__, text
138 136 title = self.title_pat.split(text)
139 137 if len(title)>1:
140 138 self.title = title[-2]
141 139
142 140 text = self.title_pat.sub('', text)
143 141 segments = self.color_pat.split(text)
144 142 segment = segments.pop(0)
145 143 self.GotoPos(self.GetLength())
146 144 self.StartStyling(self.GetLength(), 0xFF)
147 145 try:
148 146 self.AppendText(segment)
149 147 except UnicodeDecodeError:
150 148 # XXX: Do I really want to skip the exception?
151 149 pass
152 150
153 151 if segments:
154 152 for ansi_tag, text in zip(segments[::2], segments[1::2]):
155 153 self.StartStyling(self.GetLength(), 0xFF)
156 154 try:
157 155 self.AppendText(text)
158 156 except UnicodeDecodeError:
159 157 # XXX: Do I really want to skip the exception?
160 158 pass
161 159
162 160 if ansi_tag not in self.ANSI_STYLES:
163 161 style = 0
164 162 else:
165 163 style = self.ANSI_STYLES[ansi_tag][0]
166 164
167 165 self.SetStyling(len(text), style)
168 166
169 167 self.GotoPos(self.GetLength())
170 168 if refresh:
171 169 wx.Yield()
172 170
173 171
174 172 def new_prompt(self, prompt):
175 173 """ Prints a prompt at start of line, and move the start of the
176 174 current block there.
177 175
178 176 The prompt can be given with ascii escape sequences.
179 177 """
180 178 self.write(prompt)
181 179 # now we update our cursor giving end of prompt
182 180 self.current_prompt_pos = self.GetLength()
183 181 self.current_prompt_line = self.GetCurrentLine()
184 182 wx.Yield()
185 183 self.EnsureCaretVisible()
186 184
187 185
188 186 def scroll_to_bottom(self):
189 187 maxrange = self.GetScrollRange(wx.VERTICAL)
190 188 self.ScrollLines(maxrange)
191 189
192 190
193 191 def pop_completion(self, possibilities, offset=0):
194 192 """ Pops up an autocompletion menu. Offset is the offset
195 193 in characters of the position at which the menu should
196 194 appear, relativ to the cursor.
197 195 """
198 196 self.AutoCompSetIgnoreCase(False)
199 197 self.AutoCompSetAutoHide(False)
200 198 self.AutoCompSetMaxHeight(len(possibilities))
201 199 self.AutoCompShow(offset, " ".join(possibilities))
202 200
203 201
204 202 def get_line_width(self):
205 203 """ Return the width of the line in characters.
206 204 """
207 205 return self.GetSize()[0]/self.GetCharWidth()
208 206
209 207
210 208 #--------------------------------------------------------------------------
211 209 # Private API
212 210 #--------------------------------------------------------------------------
213 211
214 212 def _apply_style(self):
215 213 """ Applies the colors for the different text elements and the
216 214 carret.
217 215 """
218 216 self.SetCaretForeground(self.carret_color)
219 217
220 218 #self.StyleClearAll()
221 219 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
222 220 "fore:#FF0000,back:#0000FF,bold")
223 221 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
224 222 "fore:#000000,back:#FF0000,bold")
225 223
226 224 for style in self.ANSI_STYLES.values():
227 225 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
228 226
229 227
230 228 def _configure_scintilla(self):
231 229 self.SetEOLMode(stc.STC_EOL_LF)
232 230
233 231 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
234 232 # the widget
235 233 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
236 234 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
237 235 # Also allow Ctrl Shift "=" for poor non US keyboard users.
238 236 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
239 237 stc.STC_CMD_ZOOMIN)
240 238
241 239 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
242 240 # stc.STC_CMD_PAGEUP)
243 241
244 242 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
245 243 # stc.STC_CMD_PAGEDOWN)
246 244
247 245 # Keys: we need to clear some of the keys the that don't play
248 246 # well with a console.
249 247 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
250 248 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
251 249 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
252 250
253 251
254 252 self.SetEOLMode(stc.STC_EOL_CRLF)
255 253 self.SetWrapMode(stc.STC_WRAP_CHAR)
256 254 self.SetWrapMode(stc.STC_WRAP_WORD)
257 255 self.SetBufferedDraw(True)
258 256 self.SetUseAntiAliasing(True)
259 257 self.SetLayoutCache(stc.STC_CACHE_PAGE)
260 258 self.SetUndoCollection(False)
261 259 self.SetUseTabs(True)
262 260 self.SetIndent(4)
263 261 self.SetTabWidth(4)
264 262
265 263 self.EnsureCaretVisible()
266 264 # we don't want scintilla's autocompletion to choose
267 265 # automaticaly out of a single choice list, as we pop it up
268 266 # automaticaly
269 267 self.AutoCompSetChooseSingle(False)
270 268 self.AutoCompSetMaxHeight(10)
271 269
272 270 self.SetMargins(3, 3) #text is moved away from border with 3px
273 271 # Suppressing Scintilla margins
274 272 self.SetMarginWidth(0, 0)
275 273 self.SetMarginWidth(1, 0)
276 274 self.SetMarginWidth(2, 0)
277 275
278 276 self._apply_style()
279 277
280 278 # Xterm escape sequences
281 279 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
282 280 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
283 281
284 282 #self.SetEdgeMode(stc.STC_EDGE_LINE)
285 283 #self.SetEdgeColumn(80)
286 284
287 285 # styles
288 286 p = self.style
289 287 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
290 288 self.StyleClearAll()
291 289 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
292 290 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
293 291 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
294 292
295 293 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
296 294 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
297 295 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
298 296 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
299 297 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
300 298 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
301 299 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
302 300 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
303 301 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
304 302 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
305 303 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
306 304 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
307 305 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
308 306 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
309 307
310 308
311 309 def _on_key_down(self, event, skip=True):
312 310 """ Key press callback used for correcting behavior for
313 311 console-like interfaces: the cursor is constraint to be after
314 312 the last prompt.
315 313
316 314 Return True if event as been catched.
317 315 """
318 316 catched = True
319 317 # Intercept some specific keys.
320 318 if event.KeyCode == ord('L') and event.ControlDown() :
321 319 self.scroll_to_bottom()
322 320 elif event.KeyCode == ord('K') and event.ControlDown() :
323 321 self.input_buffer = ''
324 322 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
325 323 self.ScrollPages(-1)
326 324 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
327 325 self.ScrollPages(1)
328 326 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
329 327 self.ScrollLines(-1)
330 328 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
331 329 self.ScrollLines(1)
332 330 else:
333 331 catched = False
334 332
335 333 if self.AutoCompActive():
336 334 event.Skip()
337 335 else:
338 336 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
339 337 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
340 338 catched = True
341 339 self.CallTipCancel()
342 340 self.write('\n')
343 341 # Under windows scintilla seems to be doing funny stuff to the
344 342 # line returns here, but the getter for input_buffer filters
345 343 # this out.
346 344 if sys.platform == 'win32':
347 345 self.input_buffer = self.input_buffer
348 346 self._on_enter()
349 347
350 348 elif event.KeyCode == wx.WXK_HOME:
351 349 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
352 350 self.GotoPos(self.current_prompt_pos)
353 351 catched = True
354 352
355 353 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
356 354 # FIXME: This behavior is not ideal: if the selection
357 355 # is already started, it will jump.
358 356 self.SetSelectionStart(self.current_prompt_pos)
359 357 self.SetSelectionEnd(self.GetCurrentPos())
360 358 catched = True
361 359
362 360 elif event.KeyCode == wx.WXK_UP:
363 361 if self.GetCurrentLine() > self.current_prompt_line:
364 362 if self.GetCurrentLine() == self.current_prompt_line + 1 \
365 363 and self.GetColumn(self.GetCurrentPos()) < \
366 364 self.GetColumn(self.current_prompt_pos):
367 365 self.GotoPos(self.current_prompt_pos)
368 366 else:
369 367 event.Skip()
370 368 catched = True
371 369
372 370 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
373 371 if self.GetCurrentPos() > self.current_prompt_pos:
374 372 event.Skip()
375 373 catched = True
376 374
377 375 if skip and not catched:
378 376 # Put the cursor back in the edit region
379 377 if self.GetCurrentPos() < self.current_prompt_pos:
380 378 self.GotoPos(self.current_prompt_pos)
381 379 else:
382 380 event.Skip()
383 381
384 382 return catched
385 383
386 384
387 385 def _on_key_up(self, event, skip=True):
388 386 """ If cursor is outside the editing region, put it back.
389 387 """
390 388 event.Skip()
391 389 if self.GetCurrentPos() < self.current_prompt_pos:
392 390 self.GotoPos(self.current_prompt_pos)
393 391
394 392
395 393
396 394 if __name__ == '__main__':
397 395 # Some simple code to test the console widget.
398 396 class MainWindow(wx.Frame):
399 397 def __init__(self, parent, id, title):
400 398 wx.Frame.__init__(self, parent, id, title, size=(300,250))
401 399 self._sizer = wx.BoxSizer(wx.VERTICAL)
402 400 self.console_widget = ConsoleWidget(self)
403 401 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
404 402 self.SetSizer(self._sizer)
405 403 self.SetAutoLayout(1)
406 404 self.Show(True)
407 405
408 406 app = wx.PySimpleApp()
409 407 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
410 408 w.SetSize((780, 460))
411 409 w.Show()
412 410
413 411 app.MainLoop()
414 412
415 413
@@ -1,78 +1,78 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 import wx
7 7 from wx_frontend import WxController
8 8 import __builtin__
9 9
10 10 class IPythonXController(WxController):
11 11 """ Sub class of WxController that adds some application-specific
12 12 bindings.
13 13 """
14 14
15 debug = False
15 debug = True
16 16
17 17 def __init__(self, *args, **kwargs):
18 18 WxController.__init__(self, *args, **kwargs)
19 19 self.ipython0.ask_exit = self.do_exit
20 20
21 21
22 22 def _on_key_down(self, event, skip=True):
23 23 # Intercept Ctrl-D to quit
24 24 if event.KeyCode == ord('D') and event.ControlDown() and \
25 25 self.input_buffer == '' and \
26 26 self._input_state == 'readline':
27 27 wx.CallAfter(self.ask_exit)
28 28 else:
29 29 WxController._on_key_down(self, event, skip=skip)
30 30
31 31
32 32 def ask_exit(self):
33 33 """ Ask the user whether to exit.
34 34 """
35 35 self.write('\n')
36 36 self.capture_output()
37 37 self.ipython0.shell.exit()
38 38 self.release_output()
39 39 wx.Yield()
40 40 if not self.ipython0.exit_now:
41 41 self.new_prompt(self.input_prompt_template.substitute(
42 42 number=self.last_result['number'] + 1))
43 43
44 44
45 45 def do_exit(self):
46 46 """ Exits the interpreter, kills the windows.
47 47 """
48 48 WxController.do_exit(self)
49 49 self.release_output()
50 50 wx.CallAfter(wx.Exit)
51 51
52 52
53 53
54 54 class IPythonX(wx.Frame):
55 55 """ Main frame of the IPythonX app.
56 56 """
57 57
58 58 def __init__(self, parent, id, title):
59 59 wx.Frame.__init__(self, parent, id, title, size=(300,250))
60 60 self._sizer = wx.BoxSizer(wx.VERTICAL)
61 61 self.shell = IPythonXController(self)
62 62 self._sizer.Add(self.shell, 1, wx.EXPAND)
63 63 self.SetSizer(self._sizer)
64 64 self.SetAutoLayout(1)
65 65 self.Show(True)
66 66
67 67
68 68 def main():
69 69 app = wx.PySimpleApp()
70 70 frame = IPythonX(None, wx.ID_ANY, 'IPythonX')
71 71 frame.shell.SetFocus()
72 72 frame.shell.app = app
73 73 frame.SetSize((680, 460))
74 74
75 75 app.MainLoop()
76 76
77 77 if __name__ == '__main__':
78 78 main()
@@ -1,451 +1,465 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 from time import sleep
29 29 import sys
30 30 from threading import Lock
31 31 import string
32 32
33 33 import wx
34 34 from wx import stc
35 35
36 36 # Ipython-specific imports.
37 37 from IPython.frontend._process import PipedProcess
38 38 from console_widget import ConsoleWidget
39 39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40 40
41 41 #-------------------------------------------------------------------------------
42 42 # Constants
43 43 #-------------------------------------------------------------------------------
44 44
45 #_COMMAND_BG = '#FAFAF1' # Nice green
46 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 47 _ERROR_BG = '#FFF1F1' # Nice red
48 48
49 _RUNNING_BUFFER_MARKER = 31
49 _COMPLETE_BUFFER_MARKER = 31
50 50 _ERROR_MARKER = 30
51 _INPUT_MARKER = 29
51 52
52 53 prompt_in1 = \
53 54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
54 55
55 56 prompt_out = \
56 57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
57 58
58 59 #-------------------------------------------------------------------------------
59 60 # Classes to implement the Wx frontend
60 61 #-------------------------------------------------------------------------------
61 62 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 63 """Classes to provide a Wx frontend to the
63 64 IPython.kernel.core.interpreter.
64 65
65 66 This class inherits from ConsoleWidget, that provides a console-like
66 67 widget to provide a text-rendering widget suitable for a terminal.
67 68 """
68 69
69 70 output_prompt_template = string.Template(prompt_out)
70 71
71 72 input_prompt_template = string.Template(prompt_in1)
72 73
73 74 # Print debug info on what is happening to the console.
74 75 debug = True
75 76
76 77 # The title of the terminal, as captured through the ANSI escape
77 78 # sequences.
78 79 def _set_title(self, title):
79 80 return self.Parent.SetTitle(title)
80 81
81 82 def _get_title(self):
82 83 return self.Parent.GetTitle()
83 84
84 85 title = property(_get_title, _set_title)
85 86
86 87
87 88 # The buffer being edited.
88 89 # We are duplicating the defination here because of multiple
89 90 # inheritence
90 91 def _set_input_buffer(self, string):
91 92 return ConsoleWidget._set_input_buffer(self, string)
92 93
93 94 def _get_input_buffer(self):
94 95 """ Returns the text in current edit buffer.
95 96 """
96 97 return ConsoleWidget._get_input_buffer(self)
97 98
98 99 input_buffer = property(_get_input_buffer, _set_input_buffer)
99 100
100 101
101 102 #--------------------------------------------------------------------------
102 103 # Private Attributes
103 104 #--------------------------------------------------------------------------
104 105
105 106 # A flag governing the behavior of the input. Can be:
106 107 #
107 108 # 'readline' for readline-like behavior with a prompt
108 109 # and an edit buffer.
109 110 # 'subprocess' for sending the raw input directly to a
110 111 # subprocess.
111 112 # 'buffering' for buffering of the input, that will be used
112 113 # when the input state switches back to another state.
113 114 _input_state = 'readline'
114 115
115 116 # Attribute to store reference to the pipes of a subprocess, if we
116 117 # are running any.
117 118 _running_process = False
118 119
119 120 # A queue for writing fast streams to the screen without flooding the
120 121 # event loop
121 122 _out_buffer = []
122 123
123 124 # A lock to lock the _out_buffer to make sure we don't empty it
124 125 # while it is being swapped
125 126 _out_buffer_lock = Lock()
126 127
128 _markers = dict()
129
127 130 #--------------------------------------------------------------------------
128 131 # Public API
129 132 #--------------------------------------------------------------------------
130 133
131 134 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
132 135 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
133 136 *args, **kwds):
134 137 """ Create Shell instance.
135 138 """
136 139 ConsoleWidget.__init__(self, parent, id, pos, size, style)
137 140 PrefilterFrontEnd.__init__(self)
138 141
139 # Marker for running buffer.
140 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
141 background=_RUNNING_BUFFER_BG)
142 # Marker for complete buffer.
143 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
144 background=_COMPLETE_BUFFER_BG)
145 # Marker for current input buffer.
146 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
147 background=_INPUT_BUFFER_BG)
142 148 # Marker for tracebacks.
143 149 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
144 150 background=_ERROR_BG)
145 151
146 152 # A time for flushing the write buffer
147 153 BUFFER_FLUSH_TIMER_ID = 100
148 154 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
149 155 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
150 156
157 # Inject self in namespace, for debug
158 if self.debug:
159 self.shell.user_ns['self'] = self
160
151 161
152 162 def raw_input(self, prompt):
153 163 """ A replacement from python's raw_input.
154 164 """
155 165 self.new_prompt(prompt)
156 166 self.waiting = True
157 167 self.__old_on_enter = self._on_enter
158 168 def my_on_enter():
159 169 self.waiting = False
160 170 self._on_enter = my_on_enter
161 171 # XXX: Busy waiting, ugly.
162 172 while self.waiting:
163 173 wx.Yield()
164 174 sleep(0.1)
165 175 self._on_enter = self.__old_on_enter
166 176 self._input_state = 'buffering'
167 177 return self.input_buffer.rstrip('\n')
168 178
169 179
170 180 def system_call(self, command_string):
171 181 self._input_state = 'subprocess'
172 182 self._running_process = PipedProcess(command_string,
173 183 out_callback=self.buffered_write,
174 184 end_callback = self._end_system_call)
175 185 self._running_process.start()
176 186 # XXX: another one of these polling loops to have a blocking
177 187 # call
178 188 wx.Yield()
179 189 while self._running_process:
180 190 wx.Yield()
181 191 sleep(0.1)
182 192 # Be sure to flush the buffer.
183 193 self._buffer_flush(event=None)
184 194
185 195
186 196 def do_calltip(self):
187 197 """ Analyse current and displays useful calltip for it.
188 198 """
189 199 if self.debug:
190 200 print >>sys.__stdout__, "do_calltip"
191 201 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
192 202 symbol = self.input_buffer
193 203 symbol_string = separators.split(symbol)[-1]
194 204 base_symbol_string = symbol_string.split('.')[0]
195 205 if base_symbol_string in self.shell.user_ns:
196 206 symbol = self.shell.user_ns[base_symbol_string]
197 207 elif base_symbol_string in self.shell.user_global_ns:
198 208 symbol = self.shell.user_global_ns[base_symbol_string]
199 209 elif base_symbol_string in __builtin__.__dict__:
200 210 symbol = __builtin__.__dict__[base_symbol_string]
201 211 else:
202 212 return False
203 213 for name in symbol_string.split('.')[1:] + ['__doc__']:
204 214 symbol = getattr(symbol, name)
205 215 try:
206 216 self.AutoCompCancel()
207 217 wx.Yield()
208 218 self.CallTipShow(self.GetCurrentPos(), symbol)
209 219 except:
210 220 # The retrieve symbol couldn't be converted to a string
211 221 pass
212 222
213 223
214 224 def _popup_completion(self, create=False):
215 225 """ Updates the popup completion menu if it exists. If create is
216 226 true, open the menu.
217 227 """
218 228 if self.debug:
219 229 print >>sys.__stdout__, "_popup_completion",
220 230 line = self.input_buffer
221 231 if (self.AutoCompActive() and not line[-1] == '.') \
222 232 or create==True:
223 233 suggestion, completions = self.complete(line)
224 234 offset=0
225 235 if completions:
226 236 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
227 237 residual = complete_sep.split(line)[-1]
228 238 offset = len(residual)
229 239 self.pop_completion(completions, offset=offset)
230 240 if self.debug:
231 241 print >>sys.__stdout__, completions
232 242
233 243
234 244 def buffered_write(self, text):
235 245 """ A write method for streams, that caches the stream in order
236 246 to avoid flooding the event loop.
237 247
238 248 This can be called outside of the main loop, in separate
239 249 threads.
240 250 """
241 251 self._out_buffer_lock.acquire()
242 252 self._out_buffer.append(text)
243 253 self._out_buffer_lock.release()
244 254 if not self._buffer_flush_timer.IsRunning():
245 255 wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds
246 256
247 257
248 258 #--------------------------------------------------------------------------
249 259 # LineFrontEnd interface
250 260 #--------------------------------------------------------------------------
251 261
252 262 def execute(self, python_string, raw_string=None):
253 263 self._input_state = 'buffering'
254 264 self.CallTipCancel()
255 265 self._cursor = wx.BusyCursor()
256 266 if raw_string is None:
257 267 raw_string = python_string
258 268 end_line = self.current_prompt_line \
259 269 + max(1, len(raw_string.split('\n'))-1)
260 270 for i in range(self.current_prompt_line, end_line):
261 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
271 if i in self._markers:
272 self.MarkerDeleteHandle(self._markers[i])
273 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
262 274 # Update the display:
263 275 wx.Yield()
264 276 self.GotoPos(self.GetLength())
265 277 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
266 278
267 279
268 280 def capture_output(self):
269 281 self.__old_raw_input = __builtin__.raw_input
270 282 __builtin__.raw_input = self.raw_input
271 283 PrefilterFrontEnd.capture_output(self)
272 284
273 285
274 286 def release_output(self):
275 287 __builtin__.raw_input = self.__old_raw_input
276 PrefilterFrontEnd.capture_output(self)
288 PrefilterFrontEnd.release_output(self)
277 289
278 290
279 291 def after_execute(self):
280 292 PrefilterFrontEnd.after_execute(self)
281 293 # Clear the wait cursor
282 294 if hasattr(self, '_cursor'):
283 295 del self._cursor
284 296
285 297
286 298 def show_traceback(self):
287 299 start_line = self.GetCurrentLine()
288 300 PrefilterFrontEnd.show_traceback(self)
289 301 wx.Yield()
290 302 for i in range(start_line, self.GetCurrentLine()):
291 self.MarkerAdd(i, _ERROR_MARKER)
303 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
292 304
293 305
294 306 #--------------------------------------------------------------------------
295 307 # ConsoleWidget interface
296 308 #--------------------------------------------------------------------------
297 309
298 310 def new_prompt(self, prompt):
299 311 """ Display a new prompt, and start a new input buffer.
300 312 """
301 313 self._input_state = 'readline'
302 314 ConsoleWidget.new_prompt(self, prompt)
303 315
304 316
305 317 def write(self, *args, **kwargs):
306 318 # Avoid multiple inheritence, be explicit about which
307 319 # parent method class gets called
308 320 ConsoleWidget.write(self, *args, **kwargs)
309 321
310 322
311 323 def _on_key_down(self, event, skip=True):
312 324 """ Capture the character events, let the parent
313 325 widget handle them, and put our logic afterward.
314 326 """
315 327 # FIXME: This method needs to be broken down in smaller ones.
316 328 current_line_number = self.GetCurrentLine()
317 329 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
318 330 # Capture Control-C
319 331 if self._input_state == 'subprocess':
320 332 if self.debug:
321 333 print >>sys.__stderr__, 'Killing running process'
322 334 self._running_process.process.kill()
323 335 elif self._input_state == 'buffering':
324 336 if self.debug:
325 337 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
326 338 raise KeyboardInterrupt
327 339 # XXX: We need to make really sure we
328 340 # get back to a prompt.
329 341 elif self._input_state == 'subprocess' and (
330 342 ( event.KeyCode<256 and
331 343 not event.ControlDown() )
332 344 or
333 345 ( event.KeyCode in (ord('d'), ord('D')) and
334 346 event.ControlDown())):
335 347 # We are running a process, we redirect keys.
336 348 ConsoleWidget._on_key_down(self, event, skip=skip)
337 349 char = chr(event.KeyCode)
338 350 # Deal with some inconsistency in wx keycodes:
339 351 if char == '\r':
340 352 char = '\n'
341 353 elif not event.ShiftDown():
342 354 char = char.lower()
343 355 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
344 356 char = '\04'
345 357 self._running_process.process.stdin.write(char)
346 358 self._running_process.process.stdin.flush()
347 359 elif event.KeyCode in (ord('('), 57):
348 360 # Calltips
349 361 event.Skip()
350 362 self.do_calltip()
351 363 elif self.AutoCompActive():
352 364 event.Skip()
353 365 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
354 366 wx.CallAfter(self._popup_completion, create=True)
355 367 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
356 368 wx.WXK_RIGHT):
357 369 wx.CallAfter(self._popup_completion)
358 370 else:
359 371 # Up history
360 372 if event.KeyCode == wx.WXK_UP and (
361 373 ( current_line_number == self.current_prompt_line and
362 374 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
363 375 or event.ControlDown() ):
364 376 new_buffer = self.get_history_previous(
365 377 self.input_buffer)
366 378 if new_buffer is not None:
367 379 self.input_buffer = new_buffer
368 380 if self.GetCurrentLine() > self.current_prompt_line:
369 381 # Go to first line, for seemless history up.
370 382 self.GotoPos(self.current_prompt_pos)
371 383 # Down history
372 384 elif event.KeyCode == wx.WXK_DOWN and (
373 385 ( current_line_number == self.LineCount -1 and
374 386 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
375 387 or event.ControlDown() ):
376 388 new_buffer = self.get_history_next()
377 389 if new_buffer is not None:
378 390 self.input_buffer = new_buffer
379 391 # Tab-completion
380 392 elif event.KeyCode == ord('\t'):
381 393 last_line = self.input_buffer.split('\n')[-1]
382 394 if not re.match(r'^\s*$', last_line):
383 395 self.complete_current_input()
384 396 else:
385 397 event.Skip()
386 398 else:
387 399 ConsoleWidget._on_key_down(self, event, skip=skip)
388 400
389 401
390 402 def _on_key_up(self, event, skip=True):
391 403 """ Called when any key is released.
392 404 """
393 405 if event.KeyCode in (59, ord('.')):
394 406 # Intercepting '.'
395 407 event.Skip()
396 408 self._popup_completion(create=True)
397 409 else:
398 410 ConsoleWidget._on_key_up(self, event, skip=skip)
399 411
400 412
401 413 def _on_enter(self):
402 414 """ Called on return key down, in readline input_state.
403 415 """
404 416 if self.debug:
405 417 print >>sys.__stdout__, repr(self.input_buffer)
418 i = self.GetLineCount()
419 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
406 420 PrefilterFrontEnd._on_enter(self)
407 421
408 422
409 423 #--------------------------------------------------------------------------
410 424 # Private API
411 425 #--------------------------------------------------------------------------
412 426
413 427 def _end_system_call(self):
414 428 """ Called at the end of a system call.
415 429 """
416 430 self._input_state = 'buffering'
417 431 self._running_process = False
418 432
419 433
420 434 def _buffer_flush(self, event):
421 435 """ Called by the timer to flush the write buffer.
422 436
423 437 This is always called in the mainloop, by the wx timer.
424 438 """
425 439 self._out_buffer_lock.acquire()
426 440 _out_buffer = self._out_buffer
427 441 self._out_buffer = []
428 442 self._out_buffer_lock.release()
429 443 self.write(''.join(_out_buffer), refresh=False)
430 444 self._buffer_flush_timer.Stop()
431 445
432 446
433 447 if __name__ == '__main__':
434 448 class MainWindow(wx.Frame):
435 449 def __init__(self, parent, id, title):
436 450 wx.Frame.__init__(self, parent, id, title, size=(300,250))
437 451 self._sizer = wx.BoxSizer(wx.VERTICAL)
438 452 self.shell = WxController(self)
439 453 self._sizer.Add(self.shell, 1, wx.EXPAND)
440 454 self.SetSizer(self._sizer)
441 455 self.SetAutoLayout(1)
442 456 self.Show(True)
443 457
444 458 app = wx.PySimpleApp()
445 459 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
446 460 frame.shell.SetFocus()
447 461 frame.SetSize((680, 460))
448 462 self = frame.shell
449 463
450 464 app.MainLoop()
451 465
@@ -1,105 +1,107 b''
1 1 # encoding: utf-8
2 2
3 3 """ Trap stdout/stderr."""
4 4
5 5 __docformat__ = "restructuredtext en"
6 6
7 7 #-------------------------------------------------------------------------------
8 8 # Copyright (C) 2008 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-------------------------------------------------------------------------------
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Imports
16 16 #-------------------------------------------------------------------------------
17 17
18 18 import sys
19 19 from cStringIO import StringIO
20 20
21 21
22 22 class OutputTrap(object):
23 23 """ Object which can trap text sent to stdout and stderr.
24 24 """
25 25
26 26 def __init__(self, out=None, err=None):
27 27 # Filelike objects to store stdout/stderr text.
28 28 if out is None:
29 29 self.out = StringIO()
30 30 else:
31 31 self.out = out
32 32 if err is None:
33 33 self.err = StringIO()
34 34 else:
35 35 self.err = err
36 36
37 37 # Boolean to check if the stdout/stderr hook is set.
38 38 self.out_set = False
39 39 self.err_set = False
40 40
41 41 @property
42 42 def out_text(self):
43 43 """ Return the text currently in the stdout buffer.
44 44 """
45 45 return self.out.getvalue()
46 46
47 47 @property
48 48 def err_text(self):
49 49 """ Return the text currently in the stderr buffer.
50 50 """
51 51 return self.err.getvalue()
52 52
53 53 def set(self):
54 54 """ Set the hooks.
55 55 """
56 56
57 57 if sys.stdout is not self.out:
58 58 self._out_save = sys.stdout
59 59 sys.stdout = self.out
60 60 self.out_set = True
61 61
62 62 if sys.stderr is not self.err:
63 63 self._err_save = sys.stderr
64 64 sys.stderr = self.err
65 65 self.err_set = True
66 66
67 67 def unset(self):
68 68 """ Remove the hooks.
69 69 """
70 70
71 sys.stdout = self._out_save
71 if self.out_set:
72 sys.stdout = self._out_save
72 73 self.out_set = False
73 74
74 sys.stderr = self._err_save
75 if self.err_set:
76 sys.stderr = self._err_save
75 77 self.err_set = False
76 78
77 79 def clear(self):
78 80 """ Clear out the buffers.
79 81 """
80 82
81 83 self.out.reset()
82 84 self.out.truncate()
83 85
84 86 self.err.reset()
85 87 self.err.truncate()
86 88
87 89 def add_to_message(self, message):
88 90 """ Add the text from stdout and stderr to the message from the
89 91 interpreter to its listeners.
90 92
91 93 Parameters
92 94 ----------
93 95 message : dict
94 96 """
95 97
96 98 out_text = self.out_text
97 99 if out_text:
98 100 message['stdout'] = out_text
99 101
100 102 err_text = self.err_text
101 103 if err_text:
102 104 message['stderr'] = err_text
103 105
104 106
105 107
General Comments 0
You need to be logged in to leave comments. Login now