##// END OF EJS Templates
Fix a completion crasher (index error during completion)....
Gael Varoquaux -
Show More
@@ -1,294 +1,305 b''
1 1 """
2 2 Base front end class for all line-oriented frontends, rather than
3 3 block-oriented.
4 4
5 5 Currently this focuses on synchronous frontends.
6 6 """
7 7 __docformat__ = "restructuredtext en"
8 8
9 9 #-------------------------------------------------------------------------------
10 10 # Copyright (C) 2008 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-------------------------------------------------------------------------------
15 15
16 16 #-------------------------------------------------------------------------------
17 17 # Imports
18 18 #-------------------------------------------------------------------------------
19 19 import re
20 20
21 21 import IPython
22 22 import sys
23 23
24 24 from frontendbase import FrontEndBase
25 25 from IPython.kernel.core.interpreter import Interpreter
26 26
27 27 def common_prefix(strings):
28 28 """ Given a list of strings, return the common prefix between all
29 29 these strings.
30 30 """
31 31 ref = strings[0]
32 32 prefix = ''
33 33 for size in range(len(ref)):
34 34 test_prefix = ref[:size+1]
35 35 for string in strings[1:]:
36 36 if not string.startswith(test_prefix):
37 37 return prefix
38 38 prefix = test_prefix
39 39
40 40 return prefix
41 41
42 42 #-------------------------------------------------------------------------------
43 43 # Base class for the line-oriented front ends
44 44 #-------------------------------------------------------------------------------
45 45 class LineFrontEndBase(FrontEndBase):
46 46 """ Concrete implementation of the FrontEndBase class. This is meant
47 47 to be the base class behind all the frontend that are line-oriented,
48 48 rather than block-oriented.
49 49 """
50 50
51 51 # We need to keep the prompt number, to be able to increment
52 52 # it when there is an exception.
53 53 prompt_number = 1
54 54
55 55 # We keep a reference to the last result: it helps testing and
56 56 # programatic control of the frontend.
57 57 last_result = dict(number=0)
58 58
59 59 # The input buffer being edited
60 60 input_buffer = ''
61 61
62 62 # Set to true for debug output
63 63 debug = False
64 64
65 65 # A banner to print at startup
66 66 banner = None
67 67
68 68 #--------------------------------------------------------------------------
69 69 # FrontEndBase interface
70 70 #--------------------------------------------------------------------------
71 71
72 72 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
73 73 if shell is None:
74 74 shell = Interpreter()
75 75 FrontEndBase.__init__(self, shell=shell, history=history)
76 76
77 77 if banner is not None:
78 78 self.banner = banner
79
80 def start(self):
81 """ Put the frontend in a state where it is ready for user
82 interaction.
83 """
79 84 if self.banner is not None:
80 85 self.write(self.banner, refresh=False)
81 86
82 87 self.new_prompt(self.input_prompt_template.substitute(number=1))
83 88
84 89
85 90 def complete(self, line):
86 91 """Complete line in engine's user_ns
87 92
88 93 Parameters
89 94 ----------
90 95 line : string
91 96
92 97 Result
93 98 ------
94 99 The replacement for the line and the list of possible completions.
95 100 """
96 101 completions = self.shell.complete(line)
97 102 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
98 103 if completions:
99 104 prefix = common_prefix(completions)
100 105 residual = complete_sep.split(line)[:-1]
101 106 line = line[:-len(residual)] + prefix
102 107 return line, completions
103 108
104 109
105 110 def render_result(self, result):
106 111 """ Frontend-specific rendering of the result of a calculation
107 112 that has been sent to an engine.
108 113 """
109 114 if 'stdout' in result and result['stdout']:
110 115 self.write('\n' + result['stdout'])
111 116 if 'display' in result and result['display']:
112 117 self.write("%s%s\n" % (
113 118 self.output_prompt_template.substitute(
114 119 number=result['number']),
115 120 result['display']['pprint']
116 121 ) )
117 122
118 123
119 124 def render_error(self, failure):
120 125 """ Frontend-specific rendering of error.
121 126 """
122 127 self.write('\n\n'+str(failure)+'\n\n')
123 128 return failure
124 129
125 130
126 131 def is_complete(self, string):
127 132 """ Check if a string forms a complete, executable set of
128 133 commands.
129 134
130 135 For the line-oriented frontend, multi-line code is not executed
131 136 as soon as it is complete: the users has to enter two line
132 137 returns.
133 138 """
134 139 if string in ('', '\n'):
135 140 # Prefiltering, eg through ipython0, may return an empty
136 141 # string although some operations have been accomplished. We
137 142 # thus want to consider an empty string as a complete
138 143 # statement.
139 144 return True
140 145 elif ( len(self.input_buffer.split('\n'))>2
141 146 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
142 147 return False
143 148 else:
144 149 # Add line returns here, to make sure that the statement is
145 150 # complete.
146 151 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
147 152
148 153
149 154 def write(self, string, refresh=True):
150 155 """ Write some characters to the display.
151 156
152 157 Subclass should overide this method.
153 158
154 159 The refresh keyword argument is used in frontends with an
155 160 event loop, to choose whether the write should trigget an UI
156 161 refresh, and thus be syncrhonous, or not.
157 162 """
158 163 print >>sys.__stderr__, string
159 164
160 165
161 166 def execute(self, python_string, raw_string=None):
162 167 """ Stores the raw_string in the history, and sends the
163 168 python string to the interpreter.
164 169 """
165 170 if raw_string is None:
166 171 raw_string = python_string
167 172 # Create a false result, in case there is an exception
168 173 self.last_result = dict(number=self.prompt_number)
169 174 try:
170 175 self.history.input_cache[-1] = raw_string.rstrip()
171 176 result = self.shell.execute(python_string)
172 177 self.last_result = result
173 178 self.render_result(result)
174 179 except:
175 180 self.show_traceback()
176 181 finally:
177 182 self.after_execute()
178 183
179 184 #--------------------------------------------------------------------------
180 185 # LineFrontEndBase interface
181 186 #--------------------------------------------------------------------------
182 187
183 188 def prefilter_input(self, string):
184 189 """ Priflter the input to turn it in valid python.
185 190 """
186 191 string = string.replace('\r\n', '\n')
187 192 string = string.replace('\t', 4*' ')
188 193 # Clean the trailing whitespace
189 194 string = '\n'.join(l.rstrip() for l in string.split('\n'))
190 195 return string
191 196
192 197
193 198 def after_execute(self):
194 199 """ All the operations required after an execution to put the
195 200 terminal back in a shape where it is usable.
196 201 """
197 202 self.prompt_number += 1
198 203 self.new_prompt(self.input_prompt_template.substitute(
199 204 number=(self.last_result['number'] + 1)))
200 205 # Start a new empty history entry
201 206 self._add_history(None, '')
202 207 self.history_cursor = len(self.history.input_cache) - 1
203 208
204 209
205 210 def complete_current_input(self):
206 211 """ Do code completion on current line.
207 212 """
208 213 if self.debug:
209 214 print >>sys.__stdout__, "complete_current_input",
210 215 line = self.input_buffer
211 216 new_line, completions = self.complete(line)
212 217 if len(completions)>1:
213 self.write_completion(completions)
214 self.input_buffer = new_line
218 self.write_completion(completions, new_line=new_line)
215 219 if self.debug:
220 print >>sys.__stdout__, 'line', line
221 print >>sys.__stdout__, 'new_line', new_line
216 222 print >>sys.__stdout__, completions
217 223
218 224
219 225 def get_line_width(self):
220 226 """ Return the width of the line in characters.
221 227 """
222 228 return 80
223 229
224 230
225 def write_completion(self, possibilities):
231 def write_completion(self, possibilities, new_line=None):
226 232 """ Write the list of possible completions.
233
234 new_line is the completed input line that should be displayed
235 after the completion are writen. If None, the input_buffer
236 before the completion is used.
227 237 """
228 current_buffer = self.input_buffer
238 if new_line is None:
239 new_line = self.input_buffer
229 240
230 241 self.write('\n')
231 242 max_len = len(max(possibilities, key=len)) + 1
232 243
233 244 # Now we check how much symbol we can put on a line...
234 245 chars_per_line = self.get_line_width()
235 246 symbols_per_line = max(1, chars_per_line/max_len)
236 247
237 248 pos = 1
238 249 buf = []
239 250 for symbol in possibilities:
240 251 if pos < symbols_per_line:
241 252 buf.append(symbol.ljust(max_len))
242 253 pos += 1
243 254 else:
244 255 buf.append(symbol.rstrip() + '\n')
245 256 pos = 1
246 257 self.write(''.join(buf))
247 258 self.new_prompt(self.input_prompt_template.substitute(
248 259 number=self.last_result['number'] + 1))
249 self.input_buffer = current_buffer
260 self.input_buffer = new_line
250 261
251 262
252 263 def new_prompt(self, prompt):
253 264 """ Prints a prompt and starts a new editing buffer.
254 265
255 266 Subclasses should use this method to make sure that the
256 267 terminal is put in a state favorable for a new line
257 268 input.
258 269 """
259 270 self.input_buffer = ''
260 271 self.write(prompt)
261 272
262 273
263 274 #--------------------------------------------------------------------------
264 275 # Private API
265 276 #--------------------------------------------------------------------------
266 277
267 278 def _on_enter(self):
268 279 """ Called when the return key is pressed in a line editing
269 280 buffer.
270 281 """
271 282 current_buffer = self.input_buffer
272 283 cleaned_buffer = self.prefilter_input(current_buffer)
273 284 if self.is_complete(cleaned_buffer):
274 285 self.execute(cleaned_buffer, raw_string=current_buffer)
275 286 else:
276 287 self.input_buffer += self._get_indent_string(
277 288 current_buffer[:-1])
278 289 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
279 290 self.input_buffer += '\t'
280 291
281 292
282 293 def _get_indent_string(self, string):
283 294 """ Return the string of whitespace that prefixes a line. Used to
284 295 add the right amount of indendation when creating a new line.
285 296 """
286 297 string = string.replace('\t', ' '*4)
287 298 string = string.split('\n')[-1]
288 299 indent_chars = len(string) - len(string.lstrip())
289 300 indent_string = '\t'*(indent_chars // 4) + \
290 301 ' '*(indent_chars % 4)
291 302
292 303 return indent_string
293 304
294 305
@@ -1,223 +1,241 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 from frontendbase import FrontEndBase
27 28
28 29 from IPython.ipmaker import make_IPython
29 30 from IPython.ipapi import IPApi
30 31 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
31 32
32 33 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
33 34
34 35 from IPython.genutils import Term
35 36 import pydoc
36 37 import os
38 import sys
37 39
38 40
39 41 def mk_system_call(system_call_function, command):
40 42 """ given a os.system replacement, and a leading string command,
41 43 returns a function that will execute the command with the given
42 44 argument string.
43 45 """
44 46 def my_system_call(args):
45 47 system_call_function("%s %s" % (command, args))
46 48 return my_system_call
47 49
48 50 #-------------------------------------------------------------------------------
49 51 # Frontend class using ipython0 to do the prefiltering.
50 52 #-------------------------------------------------------------------------------
51 53 class PrefilterFrontEnd(LineFrontEndBase):
52 54 """ Class that uses ipython0 to do prefilter the input, do the
53 55 completion and the magics.
54 56
55 57 The core trick is to use an ipython0 instance to prefilter the
56 58 input, and share the namespace between the interpreter instance used
57 59 to execute the statements and the ipython0 used for code
58 60 completion...
59 61 """
62
63 debug = False
60 64
61 65 def __init__(self, ipython0=None, *args, **kwargs):
62 66 """ Parameters:
63 67 -----------
64 68
65 69 ipython0: an optional ipython0 instance to use for command
66 70 prefiltering and completion.
67 71 """
72 LineFrontEndBase.__init__(self, *args, **kwargs)
73 self.shell.output_trap = RedirectorOutputTrap(
74 out_callback=self.write,
75 err_callback=self.write,
76 )
77 self.shell.traceback_trap = SyncTracebackTrap(
78 formatters=self.shell.traceback_trap.formatters,
79 )
80
81 # Start the ipython0 instance:
68 82 self.save_output_hooks()
69 83 if ipython0 is None:
70 84 # Instanciate an IPython0 interpreter to be able to use the
71 85 # prefiltering.
72 86 # XXX: argv=[] is a bit bold.
73 ipython0 = make_IPython(argv=[])
87 ipython0 = make_IPython(argv=[],
88 user_ns=self.shell.user_ns,
89 user_global_ns=self.shell.user_global_ns)
74 90 self.ipython0 = ipython0
75 91 # Set the pager:
76 92 self.ipython0.set_hook('show_in_pager',
77 93 lambda s, string: self.write("\n" + string))
78 94 self.ipython0.write = self.write
79 95 self._ip = _ip = IPApi(self.ipython0)
80 96 # Make sure the raw system call doesn't get called, as we don't
81 97 # have a stdin accessible.
82 98 self._ip.system = self.system_call
83 99 # XXX: Muck around with magics so that they work better
84 100 # in our environment
85 101 self.ipython0.magic_ls = mk_system_call(self.system_call,
86 102 'ls -CF')
87 103 # And now clean up the mess created by ipython0
88 104 self.release_output()
105
106
89 107 if not 'banner' in kwargs and self.banner is None:
90 kwargs['banner'] = self.ipython0.BANNER + """
108 self.banner = self.ipython0.BANNER + """
91 109 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
92 110
93 LineFrontEndBase.__init__(self, *args, **kwargs)
94 111 # XXX: Hack: mix the two namespaces
95 self.shell.user_ns.update(self.ipython0.user_ns)
96 self.ipython0.user_ns = self.shell.user_ns
97 self.shell.user_global_ns.update(self.ipython0.user_global_ns)
98 self.ipython0.user_global_ns = self.shell.user_global_ns
112 # self.shell.user_ns.update(self.ipython0.user_ns)
113 # self.ipython0.user_ns = self.shell.user_ns
114 # self.shell.user_global_ns.update(self.ipython0.user_global_ns)
115 # self.ipython0.user_global_ns = self.shell.user_global_ns
116
117 # self.ipython0.user_ns.update(self.shell.user_ns)
118 # self.shell.user_ns = self.ipython0.user_ns
119 # self.ipython0.user_global_ns.update(self.shell.user_global_ns)
120 # self.shell.user_global_ns = self.ipython0.user_global_ns
99 121
100 self.shell.output_trap = RedirectorOutputTrap(
101 out_callback=self.write,
102 err_callback=self.write,
103 )
104 self.shell.traceback_trap = SyncTracebackTrap(
105 formatters=self.shell.traceback_trap.formatters,
106 )
122 self.start()
107 123
108 124 #--------------------------------------------------------------------------
109 125 # FrontEndBase interface
110 126 #--------------------------------------------------------------------------
111 127
112 128 def show_traceback(self):
113 129 """ Use ipython0 to capture the last traceback and display it.
114 130 """
115 131 self.capture_output()
116 132 self.ipython0.showtraceback()
117 133 self.release_output()
118 134
119 135
120 136 def execute(self, python_string, raw_string=None):
121 137 if self.debug:
122 138 print 'Executing Python code:', repr(python_string)
123 139 self.capture_output()
124 140 LineFrontEndBase.execute(self, python_string,
125 141 raw_string=raw_string)
126 142 self.release_output()
127 143
128 144
129 145 def save_output_hooks(self):
130 146 """ Store all the output hooks we can think of, to be able to
131 147 restore them.
132 148
133 149 We need to do this early, as starting the ipython0 instance will
134 150 screw ouput hooks.
135 151 """
136 152 self.__old_cout_write = Term.cout.write
137 153 self.__old_cerr_write = Term.cerr.write
138 154 self.__old_stdout = sys.stdout
139 155 self.__old_stderr= sys.stderr
140 156 self.__old_help_output = pydoc.help.output
141 157 self.__old_display_hook = sys.displayhook
142 158
143 159
144 160 def capture_output(self):
145 161 """ Capture all the output mechanisms we can think of.
146 162 """
147 163 self.save_output_hooks()
148 164 Term.cout.write = self.write
149 165 Term.cerr.write = self.write
150 166 sys.stdout = Term.cout
151 167 sys.stderr = Term.cerr
152 168 pydoc.help.output = self.shell.output_trap.out
153 169
154 170
155 171 def release_output(self):
156 172 """ Release all the different captures we have made.
157 173 """
158 174 Term.cout.write = self.__old_cout_write
159 175 Term.cerr.write = self.__old_cerr_write
160 176 sys.stdout = self.__old_stdout
161 177 sys.stderr = self.__old_stderr
162 178 pydoc.help.output = self.__old_help_output
163 179 sys.displayhook = self.__old_display_hook
164 180
165 181
166 182 def complete(self, line):
183 # FIXME: This should be factored out in the linefrontendbase
184 # method.
167 185 word = line.split('\n')[-1].split(' ')[-1]
168 186 completions = self.ipython0.complete(word)
169 187 # FIXME: The proper sort should be done in the complete method.
170 188 key = lambda x: x.replace('_', '')
171 189 completions.sort(key=key)
172 190 if completions:
173 191 prefix = common_prefix(completions)
174 192 line = line[:-len(word)] + prefix
175 193 return line, completions
176 194
177 195
178 196 #--------------------------------------------------------------------------
179 197 # LineFrontEndBase interface
180 198 #--------------------------------------------------------------------------
181 199
182 200 def prefilter_input(self, input_string):
183 201 """ Using IPython0 to prefilter the commands to turn them
184 202 in executable statements that are valid Python strings.
185 203 """
186 204 input_string = LineFrontEndBase.prefilter_input(self, input_string)
187 205 filtered_lines = []
188 206 # The IPython0 prefilters sometime produce output. We need to
189 207 # capture it.
190 208 self.capture_output()
191 209 self.last_result = dict(number=self.prompt_number)
192 210 try:
193 211 for line in input_string.split('\n'):
194 212 filtered_lines.append(
195 213 self.ipython0.prefilter(line, False).rstrip())
196 214 except:
197 215 # XXX: probably not the right thing to do.
198 216 self.ipython0.showsyntaxerror()
199 217 self.after_execute()
200 218 finally:
201 219 self.release_output()
202 220
203 221 # Clean up the trailing whitespace, to avoid indentation errors
204 222 filtered_string = '\n'.join(filtered_lines)
205 223 return filtered_string
206 224
207 225
208 226 #--------------------------------------------------------------------------
209 227 # PrefilterFrontEnd interface
210 228 #--------------------------------------------------------------------------
211 229
212 230 def system_call(self, command_string):
213 231 """ Allows for frontend to define their own system call, to be
214 232 able capture output and redirect input.
215 233 """
216 234 return os.system(command_string)
217 235
218 236
219 237 def do_exit(self):
220 238 """ Exit the shell, cleanup and save the history.
221 239 """
222 240 self.ipython0.atexit_operations()
223 241
@@ -1,510 +1,511 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 45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 47 _ERROR_BG = '#FFF1F1' # Nice red
48 48
49 49 _COMPLETE_BUFFER_MARKER = 31
50 50 _ERROR_MARKER = 30
51 51 _INPUT_MARKER = 29
52 52
53 53 prompt_in1 = \
54 54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
55 55
56 56 prompt_out = \
57 57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
58 58
59 59 #-------------------------------------------------------------------------------
60 60 # Classes to implement the Wx frontend
61 61 #-------------------------------------------------------------------------------
62 62 class WxController(ConsoleWidget, PrefilterFrontEnd):
63 63 """Classes to provide a Wx frontend to the
64 64 IPython.kernel.core.interpreter.
65 65
66 66 This class inherits from ConsoleWidget, that provides a console-like
67 67 widget to provide a text-rendering widget suitable for a terminal.
68 68 """
69 69
70 70 output_prompt_template = string.Template(prompt_out)
71 71
72 72 input_prompt_template = string.Template(prompt_in1)
73 73
74 74 # Print debug info on what is happening to the console.
75 75 debug = False
76 76
77 77 # The title of the terminal, as captured through the ANSI escape
78 78 # sequences.
79 79 def _set_title(self, title):
80 80 return self.Parent.SetTitle(title)
81 81
82 82 def _get_title(self):
83 83 return self.Parent.GetTitle()
84 84
85 85 title = property(_get_title, _set_title)
86 86
87 87
88 88 # The buffer being edited.
89 89 # We are duplicating the definition here because of multiple
90 90 # inheritence
91 91 def _set_input_buffer(self, string):
92 92 ConsoleWidget._set_input_buffer(self, string)
93 93 self._colorize_input_buffer()
94 94
95 95 def _get_input_buffer(self):
96 96 """ Returns the text in current edit buffer.
97 97 """
98 98 return ConsoleWidget._get_input_buffer(self)
99 99
100 100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101 101
102 102
103 103 #--------------------------------------------------------------------------
104 104 # Private Attributes
105 105 #--------------------------------------------------------------------------
106 106
107 107 # A flag governing the behavior of the input. Can be:
108 108 #
109 109 # 'readline' for readline-like behavior with a prompt
110 110 # and an edit buffer.
111 111 # 'raw_input' similar to readline, but triggered by a raw-input
112 112 # call. Can be used by subclasses to act differently.
113 113 # 'subprocess' for sending the raw input directly to a
114 114 # subprocess.
115 115 # 'buffering' for buffering of the input, that will be used
116 116 # when the input state switches back to another state.
117 117 _input_state = 'readline'
118 118
119 119 # Attribute to store reference to the pipes of a subprocess, if we
120 120 # are running any.
121 121 _running_process = False
122 122
123 123 # A queue for writing fast streams to the screen without flooding the
124 124 # event loop
125 125 _out_buffer = []
126 126
127 127 # A lock to lock the _out_buffer to make sure we don't empty it
128 128 # while it is being swapped
129 129 _out_buffer_lock = Lock()
130 130
131 # The different line markers used to higlight the prompts.
131 132 _markers = dict()
132 133
133 134 #--------------------------------------------------------------------------
134 135 # Public API
135 136 #--------------------------------------------------------------------------
136 137
137 138 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 139 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
139 140 *args, **kwds):
140 141 """ Create Shell instance.
141 142 """
142 143 ConsoleWidget.__init__(self, parent, id, pos, size, style)
143 144 PrefilterFrontEnd.__init__(self, **kwds)
144 145
145 146 # Marker for complete buffer.
146 147 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
147 148 background=_COMPLETE_BUFFER_BG)
148 149 # Marker for current input buffer.
149 150 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
150 151 background=_INPUT_BUFFER_BG)
151 152 # Marker for tracebacks.
152 153 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
153 154 background=_ERROR_BG)
154 155
155 156 # A time for flushing the write buffer
156 157 BUFFER_FLUSH_TIMER_ID = 100
157 158 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
158 159 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
159 160
160 161 if 'debug' in kwds:
161 162 self.debug = kwds['debug']
162 163 kwds.pop('debug')
163 164
164 165 # Inject self in namespace, for debug
165 166 if self.debug:
166 167 self.shell.user_ns['self'] = self
167 168
168 169
169 170 def raw_input(self, prompt):
170 171 """ A replacement from python's raw_input.
171 172 """
172 173 self.new_prompt(prompt)
173 174 self._input_state = 'raw_input'
174 175 if hasattr(self, '_cursor'):
175 176 del self._cursor
176 177 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
177 178 self.waiting = True
178 179 self.__old_on_enter = self._on_enter
179 180 def my_on_enter():
180 181 self.waiting = False
181 182 self._on_enter = my_on_enter
182 183 # XXX: Busy waiting, ugly.
183 184 while self.waiting:
184 185 wx.Yield()
185 186 sleep(0.1)
186 187 self._on_enter = self.__old_on_enter
187 188 self._input_state = 'buffering'
188 189 self._cursor = wx.BusyCursor()
189 190 return self.input_buffer.rstrip('\n')
190 191
191 192
192 193 def system_call(self, command_string):
193 194 self._input_state = 'subprocess'
194 195 self._running_process = PipedProcess(command_string,
195 196 out_callback=self.buffered_write,
196 197 end_callback = self._end_system_call)
197 198 self._running_process.start()
198 199 # XXX: another one of these polling loops to have a blocking
199 200 # call
200 201 wx.Yield()
201 202 while self._running_process:
202 203 wx.Yield()
203 204 sleep(0.1)
204 205 # Be sure to flush the buffer.
205 206 self._buffer_flush(event=None)
206 207
207 208
208 209 def do_calltip(self):
209 210 """ Analyse current and displays useful calltip for it.
210 211 """
211 212 if self.debug:
212 213 print >>sys.__stdout__, "do_calltip"
213 214 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
214 215 symbol = self.input_buffer
215 216 symbol_string = separators.split(symbol)[-1]
216 217 base_symbol_string = symbol_string.split('.')[0]
217 218 if base_symbol_string in self.shell.user_ns:
218 219 symbol = self.shell.user_ns[base_symbol_string]
219 220 elif base_symbol_string in self.shell.user_global_ns:
220 221 symbol = self.shell.user_global_ns[base_symbol_string]
221 222 elif base_symbol_string in __builtin__.__dict__:
222 223 symbol = __builtin__.__dict__[base_symbol_string]
223 224 else:
224 225 return False
225 226 try:
226 227 for name in symbol_string.split('.')[1:] + ['__doc__']:
227 228 symbol = getattr(symbol, name)
228 229 self.AutoCompCancel()
229 230 wx.Yield()
230 231 self.CallTipShow(self.GetCurrentPos(), symbol)
231 232 except:
232 233 # The retrieve symbol couldn't be converted to a string
233 234 pass
234 235
235 236
236 237 def _popup_completion(self, create=False):
237 238 """ Updates the popup completion menu if it exists. If create is
238 239 true, open the menu.
239 240 """
240 241 if self.debug:
241 print >>sys.__stdout__, "_popup_completion",
242 print >>sys.__stdout__, "_popup_completion"
242 243 line = self.input_buffer
243 if (self.AutoCompActive() and not line[-1] == '.') \
244 if (self.AutoCompActive() and line and not line[-1] == '.') \
244 245 or create==True:
245 246 suggestion, completions = self.complete(line)
246 247 offset=0
247 248 if completions:
248 249 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
249 250 residual = complete_sep.split(line)[-1]
250 251 offset = len(residual)
251 252 self.pop_completion(completions, offset=offset)
252 253 if self.debug:
253 254 print >>sys.__stdout__, completions
254 255
255 256
256 257 def buffered_write(self, text):
257 258 """ A write method for streams, that caches the stream in order
258 259 to avoid flooding the event loop.
259 260
260 261 This can be called outside of the main loop, in separate
261 262 threads.
262 263 """
263 264 self._out_buffer_lock.acquire()
264 265 self._out_buffer.append(text)
265 266 self._out_buffer_lock.release()
266 267 if not self._buffer_flush_timer.IsRunning():
267 268 wx.CallAfter(self._buffer_flush_timer.Start,
268 269 milliseconds=100, oneShot=True)
269 270
270 271
271 272 #--------------------------------------------------------------------------
272 273 # LineFrontEnd interface
273 274 #--------------------------------------------------------------------------
274 275
275 276 def execute(self, python_string, raw_string=None):
276 277 self._input_state = 'buffering'
277 278 self.CallTipCancel()
278 279 self._cursor = wx.BusyCursor()
279 280 if raw_string is None:
280 281 raw_string = python_string
281 282 end_line = self.current_prompt_line \
282 283 + max(1, len(raw_string.split('\n'))-1)
283 284 for i in range(self.current_prompt_line, end_line):
284 285 if i in self._markers:
285 286 self.MarkerDeleteHandle(self._markers[i])
286 287 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
287 288 # Update the display:
288 289 wx.Yield()
289 290 self.GotoPos(self.GetLength())
290 291 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
291 292
292 293 def save_output_hooks(self):
293 294 self.__old_raw_input = __builtin__.raw_input
294 295 PrefilterFrontEnd.save_output_hooks(self)
295 296
296 297 def capture_output(self):
297 298 __builtin__.raw_input = self.raw_input
298 299 self.SetLexer(stc.STC_LEX_NULL)
299 300 PrefilterFrontEnd.capture_output(self)
300 301
301 302
302 303 def release_output(self):
303 304 __builtin__.raw_input = self.__old_raw_input
304 305 PrefilterFrontEnd.release_output(self)
305 306 self.SetLexer(stc.STC_LEX_PYTHON)
306 307
307 308
308 309 def after_execute(self):
309 310 PrefilterFrontEnd.after_execute(self)
310 311 # Clear the wait cursor
311 312 if hasattr(self, '_cursor'):
312 313 del self._cursor
313 314 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
314 315
315 316
316 317 def show_traceback(self):
317 318 start_line = self.GetCurrentLine()
318 319 PrefilterFrontEnd.show_traceback(self)
319 320 wx.Yield()
320 321 for i in range(start_line, self.GetCurrentLine()):
321 322 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
322 323
323 324
324 325 #--------------------------------------------------------------------------
325 326 # ConsoleWidget interface
326 327 #--------------------------------------------------------------------------
327 328
328 329 def new_prompt(self, prompt):
329 330 """ Display a new prompt, and start a new input buffer.
330 331 """
331 332 self._input_state = 'readline'
332 333 ConsoleWidget.new_prompt(self, prompt)
333 334 i = self.current_prompt_line
334 335 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
335 336
336 337
337 338 def write(self, *args, **kwargs):
338 339 # Avoid multiple inheritence, be explicit about which
339 340 # parent method class gets called
340 341 ConsoleWidget.write(self, *args, **kwargs)
341 342
342 343
343 344 def _on_key_down(self, event, skip=True):
344 345 """ Capture the character events, let the parent
345 346 widget handle them, and put our logic afterward.
346 347 """
347 348 # FIXME: This method needs to be broken down in smaller ones.
348 349 current_line_number = self.GetCurrentLine()
349 350 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
350 351 # Capture Control-C
351 352 if self._input_state == 'subprocess':
352 353 if self.debug:
353 354 print >>sys.__stderr__, 'Killing running process'
354 355 self._running_process.process.kill()
355 356 elif self._input_state == 'buffering':
356 357 if self.debug:
357 358 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
358 359 raise KeyboardInterrupt
359 360 # XXX: We need to make really sure we
360 361 # get back to a prompt.
361 362 elif self._input_state == 'subprocess' and (
362 363 ( event.KeyCode<256 and
363 364 not event.ControlDown() )
364 365 or
365 366 ( event.KeyCode in (ord('d'), ord('D')) and
366 367 event.ControlDown())):
367 368 # We are running a process, we redirect keys.
368 369 ConsoleWidget._on_key_down(self, event, skip=skip)
369 370 char = chr(event.KeyCode)
370 371 # Deal with some inconsistency in wx keycodes:
371 372 if char == '\r':
372 373 char = '\n'
373 374 elif not event.ShiftDown():
374 375 char = char.lower()
375 376 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
376 377 char = '\04'
377 378 self._running_process.process.stdin.write(char)
378 379 self._running_process.process.stdin.flush()
379 380 elif event.KeyCode in (ord('('), 57):
380 381 # Calltips
381 382 event.Skip()
382 383 self.do_calltip()
383 384 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
384 385 event.Skip()
385 386 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
386 387 wx.CallAfter(self._popup_completion, create=True)
387 388 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
388 389 wx.WXK_RIGHT, wx.WXK_ESCAPE):
389 390 wx.CallAfter(self._popup_completion)
390 391 else:
391 392 # Up history
392 393 if event.KeyCode == wx.WXK_UP and (
393 394 ( current_line_number == self.current_prompt_line and
394 395 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
395 396 or event.ControlDown() ):
396 397 new_buffer = self.get_history_previous(
397 398 self.input_buffer)
398 399 if new_buffer is not None:
399 400 self.input_buffer = new_buffer
400 401 if self.GetCurrentLine() > self.current_prompt_line:
401 402 # Go to first line, for seemless history up.
402 403 self.GotoPos(self.current_prompt_pos)
403 404 # Down history
404 405 elif event.KeyCode == wx.WXK_DOWN and (
405 406 ( current_line_number == self.LineCount -1 and
406 407 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
407 408 or event.ControlDown() ):
408 409 new_buffer = self.get_history_next()
409 410 if new_buffer is not None:
410 411 self.input_buffer = new_buffer
411 412 # Tab-completion
412 413 elif event.KeyCode == ord('\t'):
413 414 last_line = self.input_buffer.split('\n')[-1]
414 415 if not re.match(r'^\s*$', last_line):
415 416 self.complete_current_input()
416 417 if self.AutoCompActive():
417 418 wx.CallAfter(self._popup_completion, create=True)
418 419 else:
419 420 event.Skip()
420 421 else:
421 422 ConsoleWidget._on_key_down(self, event, skip=skip)
422 423
423 424
424 425 def _on_key_up(self, event, skip=True):
425 426 """ Called when any key is released.
426 427 """
427 428 if event.KeyCode in (59, ord('.')):
428 429 # Intercepting '.'
429 430 event.Skip()
430 self._popup_completion(create=True)
431 wx.CallAfter(self._popup_completion, create=True)
431 432 else:
432 433 ConsoleWidget._on_key_up(self, event, skip=skip)
433 434
434 435
435 436 def _on_enter(self):
436 437 """ Called on return key down, in readline input_state.
437 438 """
438 439 if self.debug:
439 440 print >>sys.__stdout__, repr(self.input_buffer)
440 441 PrefilterFrontEnd._on_enter(self)
441 442
442 443
443 444 #--------------------------------------------------------------------------
444 445 # EditWindow API
445 446 #--------------------------------------------------------------------------
446 447
447 448 def OnUpdateUI(self, event):
448 449 """ Override the OnUpdateUI of the EditWindow class, to prevent
449 450 syntax highlighting both for faster redraw, and for more
450 451 consistent look and feel.
451 452 """
452 453 if not self._input_state == 'readline':
453 454 ConsoleWidget.OnUpdateUI(self, event)
454 455
455 456 #--------------------------------------------------------------------------
456 457 # Private API
457 458 #--------------------------------------------------------------------------
458 459
459 460 def _end_system_call(self):
460 461 """ Called at the end of a system call.
461 462 """
462 463 self._input_state = 'buffering'
463 464 self._running_process = False
464 465
465 466
466 467 def _buffer_flush(self, event):
467 468 """ Called by the timer to flush the write buffer.
468 469
469 470 This is always called in the mainloop, by the wx timer.
470 471 """
471 472 self._out_buffer_lock.acquire()
472 473 _out_buffer = self._out_buffer
473 474 self._out_buffer = []
474 475 self._out_buffer_lock.release()
475 476 self.write(''.join(_out_buffer), refresh=False)
476 477
477 478
478 479 def _colorize_input_buffer(self):
479 480 """ Keep the input buffer lines at a bright color.
480 481 """
481 482 if not self._input_state in ('readline', 'raw_input'):
482 483 return
483 484 end_line = self.GetCurrentLine()
484 485 if not sys.platform == 'win32':
485 486 end_line += 1
486 487 for i in range(self.current_prompt_line, end_line):
487 488 if i in self._markers:
488 489 self.MarkerDeleteHandle(self._markers[i])
489 490 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
490 491
491 492
492 493 if __name__ == '__main__':
493 494 class MainWindow(wx.Frame):
494 495 def __init__(self, parent, id, title):
495 496 wx.Frame.__init__(self, parent, id, title, size=(300,250))
496 497 self._sizer = wx.BoxSizer(wx.VERTICAL)
497 498 self.shell = WxController(self)
498 499 self._sizer.Add(self.shell, 1, wx.EXPAND)
499 500 self.SetSizer(self._sizer)
500 501 self.SetAutoLayout(1)
501 502 self.Show(True)
502 503
503 504 app = wx.PySimpleApp()
504 505 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
505 506 frame.shell.SetFocus()
506 507 frame.SetSize((680, 460))
507 508 self = frame.shell
508 509
509 510 app.MainLoop()
510 511
General Comments 0
You need to be logged in to leave comments. Login now