##// END OF EJS Templates
Add a banner.
Gael Varoquaux -
Show More
@@ -1,286 +1,294 b''
1 """
1 """
2 Base front end class for all line-oriented frontends, rather than
2 Base front end class for all line-oriented frontends, rather than
3 block-oriented.
3 block-oriented.
4
4
5 Currently this focuses on synchronous frontends.
5 Currently this focuses on synchronous frontends.
6 """
6 """
7 __docformat__ = "restructuredtext en"
7 __docformat__ = "restructuredtext en"
8
8
9 #-------------------------------------------------------------------------------
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
10 # Copyright (C) 2008 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15
15
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 import re
19 import re
20
20
21 import IPython
21 import IPython
22 import sys
22 import sys
23
23
24 from frontendbase import FrontEndBase
24 from frontendbase import FrontEndBase
25 from IPython.kernel.core.interpreter import Interpreter
25 from IPython.kernel.core.interpreter import Interpreter
26
26
27 def common_prefix(strings):
27 def common_prefix(strings):
28 """ Given a list of strings, return the common prefix between all
28 """ Given a list of strings, return the common prefix between all
29 these strings.
29 these strings.
30 """
30 """
31 ref = strings[0]
31 ref = strings[0]
32 prefix = ''
32 prefix = ''
33 for size in range(len(ref)):
33 for size in range(len(ref)):
34 test_prefix = ref[:size+1]
34 test_prefix = ref[:size+1]
35 for string in strings[1:]:
35 for string in strings[1:]:
36 if not string.startswith(test_prefix):
36 if not string.startswith(test_prefix):
37 return prefix
37 return prefix
38 prefix = test_prefix
38 prefix = test_prefix
39
39
40 return prefix
40 return prefix
41
41
42 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
43 # Base class for the line-oriented front ends
43 # Base class for the line-oriented front ends
44 #-------------------------------------------------------------------------------
44 #-------------------------------------------------------------------------------
45 class LineFrontEndBase(FrontEndBase):
45 class LineFrontEndBase(FrontEndBase):
46 """ Concrete implementation of the FrontEndBase class. This is meant
46 """ Concrete implementation of the FrontEndBase class. This is meant
47 to be the base class behind all the frontend that are line-oriented,
47 to be the base class behind all the frontend that are line-oriented,
48 rather than block-oriented.
48 rather than block-oriented.
49 """
49 """
50
50
51 # We need to keep the prompt number, to be able to increment
51 # We need to keep the prompt number, to be able to increment
52 # it when there is an exception.
52 # it when there is an exception.
53 prompt_number = 1
53 prompt_number = 1
54
54
55 # We keep a reference to the last result: it helps testing and
55 # We keep a reference to the last result: it helps testing and
56 # programatic control of the frontend.
56 # programatic control of the frontend.
57 last_result = dict(number=0)
57 last_result = dict(number=0)
58
58
59 # The input buffer being edited
59 # The input buffer being edited
60 input_buffer = ''
60 input_buffer = ''
61
61
62 # Set to true for debug output
62 # Set to true for debug output
63 debug = False
63 debug = False
64
64
65 # A banner to print at startup
66 banner = None
67
65 #--------------------------------------------------------------------------
68 #--------------------------------------------------------------------------
66 # FrontEndBase interface
69 # FrontEndBase interface
67 #--------------------------------------------------------------------------
70 #--------------------------------------------------------------------------
68
71
69 def __init__(self, shell=None, history=None, *args, **kwargs):
72 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
70 if shell is None:
73 if shell is None:
71 shell = Interpreter()
74 shell = Interpreter()
72 FrontEndBase.__init__(self, shell=shell, history=history)
75 FrontEndBase.__init__(self, shell=shell, history=history)
73
76
77 if banner is not None:
78 self.banner = banner
79 if self.banner is not None:
80 self.write(self.banner, refresh=False)
81
74 self.new_prompt(self.input_prompt_template.substitute(number=1))
82 self.new_prompt(self.input_prompt_template.substitute(number=1))
75
83
76
84
77 def complete(self, line):
85 def complete(self, line):
78 """Complete line in engine's user_ns
86 """Complete line in engine's user_ns
79
87
80 Parameters
88 Parameters
81 ----------
89 ----------
82 line : string
90 line : string
83
91
84 Result
92 Result
85 ------
93 ------
86 The replacement for the line and the list of possible completions.
94 The replacement for the line and the list of possible completions.
87 """
95 """
88 completions = self.shell.complete(line)
96 completions = self.shell.complete(line)
89 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
97 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
90 if completions:
98 if completions:
91 prefix = common_prefix(completions)
99 prefix = common_prefix(completions)
92 residual = complete_sep.split(line)[:-1]
100 residual = complete_sep.split(line)[:-1]
93 line = line[:-len(residual)] + prefix
101 line = line[:-len(residual)] + prefix
94 return line, completions
102 return line, completions
95
103
96
104
97 def render_result(self, result):
105 def render_result(self, result):
98 """ Frontend-specific rendering of the result of a calculation
106 """ Frontend-specific rendering of the result of a calculation
99 that has been sent to an engine.
107 that has been sent to an engine.
100 """
108 """
101 if 'stdout' in result and result['stdout']:
109 if 'stdout' in result and result['stdout']:
102 self.write('\n' + result['stdout'])
110 self.write('\n' + result['stdout'])
103 if 'display' in result and result['display']:
111 if 'display' in result and result['display']:
104 self.write("%s%s\n" % (
112 self.write("%s%s\n" % (
105 self.output_prompt_template.substitute(
113 self.output_prompt_template.substitute(
106 number=result['number']),
114 number=result['number']),
107 result['display']['pprint']
115 result['display']['pprint']
108 ) )
116 ) )
109
117
110
118
111 def render_error(self, failure):
119 def render_error(self, failure):
112 """ Frontend-specific rendering of error.
120 """ Frontend-specific rendering of error.
113 """
121 """
114 self.write('\n\n'+str(failure)+'\n\n')
122 self.write('\n\n'+str(failure)+'\n\n')
115 return failure
123 return failure
116
124
117
125
118 def is_complete(self, string):
126 def is_complete(self, string):
119 """ Check if a string forms a complete, executable set of
127 """ Check if a string forms a complete, executable set of
120 commands.
128 commands.
121
129
122 For the line-oriented frontend, multi-line code is not executed
130 For the line-oriented frontend, multi-line code is not executed
123 as soon as it is complete: the users has to enter two line
131 as soon as it is complete: the users has to enter two line
124 returns.
132 returns.
125 """
133 """
126 if string in ('', '\n'):
134 if string in ('', '\n'):
127 # Prefiltering, eg through ipython0, may return an empty
135 # Prefiltering, eg through ipython0, may return an empty
128 # string although some operations have been accomplished. We
136 # string although some operations have been accomplished. We
129 # thus want to consider an empty string as a complete
137 # thus want to consider an empty string as a complete
130 # statement.
138 # statement.
131 return True
139 return True
132 elif ( len(self.input_buffer.split('\n'))>2
140 elif ( len(self.input_buffer.split('\n'))>2
133 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
141 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
134 return False
142 return False
135 else:
143 else:
136 # Add line returns here, to make sure that the statement is
144 # Add line returns here, to make sure that the statement is
137 # complete.
145 # complete.
138 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
146 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
139
147
140
148
141 def write(self, string, refresh=True):
149 def write(self, string, refresh=True):
142 """ Write some characters to the display.
150 """ Write some characters to the display.
143
151
144 Subclass should overide this method.
152 Subclass should overide this method.
145
153
146 The refresh keyword argument is used in frontends with an
154 The refresh keyword argument is used in frontends with an
147 event loop, to choose whether the write should trigget an UI
155 event loop, to choose whether the write should trigget an UI
148 refresh, and thus be syncrhonous, or not.
156 refresh, and thus be syncrhonous, or not.
149 """
157 """
150 print >>sys.__stderr__, string
158 print >>sys.__stderr__, string
151
159
152
160
153 def execute(self, python_string, raw_string=None):
161 def execute(self, python_string, raw_string=None):
154 """ Stores the raw_string in the history, and sends the
162 """ Stores the raw_string in the history, and sends the
155 python string to the interpreter.
163 python string to the interpreter.
156 """
164 """
157 if raw_string is None:
165 if raw_string is None:
158 raw_string = python_string
166 raw_string = python_string
159 # Create a false result, in case there is an exception
167 # Create a false result, in case there is an exception
160 self.last_result = dict(number=self.prompt_number)
168 self.last_result = dict(number=self.prompt_number)
161 try:
169 try:
162 self.history.input_cache[-1] = raw_string.rstrip()
170 self.history.input_cache[-1] = raw_string.rstrip()
163 result = self.shell.execute(python_string)
171 result = self.shell.execute(python_string)
164 self.last_result = result
172 self.last_result = result
165 self.render_result(result)
173 self.render_result(result)
166 except:
174 except:
167 self.show_traceback()
175 self.show_traceback()
168 finally:
176 finally:
169 self.after_execute()
177 self.after_execute()
170
178
171 #--------------------------------------------------------------------------
179 #--------------------------------------------------------------------------
172 # LineFrontEndBase interface
180 # LineFrontEndBase interface
173 #--------------------------------------------------------------------------
181 #--------------------------------------------------------------------------
174
182
175 def prefilter_input(self, string):
183 def prefilter_input(self, string):
176 """ Priflter the input to turn it in valid python.
184 """ Priflter the input to turn it in valid python.
177 """
185 """
178 string = string.replace('\r\n', '\n')
186 string = string.replace('\r\n', '\n')
179 string = string.replace('\t', 4*' ')
187 string = string.replace('\t', 4*' ')
180 # Clean the trailing whitespace
188 # Clean the trailing whitespace
181 string = '\n'.join(l.rstrip() for l in string.split('\n'))
189 string = '\n'.join(l.rstrip() for l in string.split('\n'))
182 return string
190 return string
183
191
184
192
185 def after_execute(self):
193 def after_execute(self):
186 """ All the operations required after an execution to put the
194 """ All the operations required after an execution to put the
187 terminal back in a shape where it is usable.
195 terminal back in a shape where it is usable.
188 """
196 """
189 self.prompt_number += 1
197 self.prompt_number += 1
190 self.new_prompt(self.input_prompt_template.substitute(
198 self.new_prompt(self.input_prompt_template.substitute(
191 number=(self.last_result['number'] + 1)))
199 number=(self.last_result['number'] + 1)))
192 # Start a new empty history entry
200 # Start a new empty history entry
193 self._add_history(None, '')
201 self._add_history(None, '')
194 self.history_cursor = len(self.history.input_cache) - 1
202 self.history_cursor = len(self.history.input_cache) - 1
195
203
196
204
197 def complete_current_input(self):
205 def complete_current_input(self):
198 """ Do code completion on current line.
206 """ Do code completion on current line.
199 """
207 """
200 if self.debug:
208 if self.debug:
201 print >>sys.__stdout__, "complete_current_input",
209 print >>sys.__stdout__, "complete_current_input",
202 line = self.input_buffer
210 line = self.input_buffer
203 new_line, completions = self.complete(line)
211 new_line, completions = self.complete(line)
204 if len(completions)>1:
212 if len(completions)>1:
205 self.write_completion(completions)
213 self.write_completion(completions)
206 self.input_buffer = new_line
214 self.input_buffer = new_line
207 if self.debug:
215 if self.debug:
208 print >>sys.__stdout__, completions
216 print >>sys.__stdout__, completions
209
217
210
218
211 def get_line_width(self):
219 def get_line_width(self):
212 """ Return the width of the line in characters.
220 """ Return the width of the line in characters.
213 """
221 """
214 return 80
222 return 80
215
223
216
224
217 def write_completion(self, possibilities):
225 def write_completion(self, possibilities):
218 """ Write the list of possible completions.
226 """ Write the list of possible completions.
219 """
227 """
220 current_buffer = self.input_buffer
228 current_buffer = self.input_buffer
221
229
222 self.write('\n')
230 self.write('\n')
223 max_len = len(max(possibilities, key=len)) + 1
231 max_len = len(max(possibilities, key=len)) + 1
224
232
225 # Now we check how much symbol we can put on a line...
233 # Now we check how much symbol we can put on a line...
226 chars_per_line = self.get_line_width()
234 chars_per_line = self.get_line_width()
227 symbols_per_line = max(1, chars_per_line/max_len)
235 symbols_per_line = max(1, chars_per_line/max_len)
228
236
229 pos = 1
237 pos = 1
230 buf = []
238 buf = []
231 for symbol in possibilities:
239 for symbol in possibilities:
232 if pos < symbols_per_line:
240 if pos < symbols_per_line:
233 buf.append(symbol.ljust(max_len))
241 buf.append(symbol.ljust(max_len))
234 pos += 1
242 pos += 1
235 else:
243 else:
236 buf.append(symbol.rstrip() + '\n')
244 buf.append(symbol.rstrip() + '\n')
237 pos = 1
245 pos = 1
238 self.write(''.join(buf))
246 self.write(''.join(buf))
239 self.new_prompt(self.input_prompt_template.substitute(
247 self.new_prompt(self.input_prompt_template.substitute(
240 number=self.last_result['number'] + 1))
248 number=self.last_result['number'] + 1))
241 self.input_buffer = current_buffer
249 self.input_buffer = current_buffer
242
250
243
251
244 def new_prompt(self, prompt):
252 def new_prompt(self, prompt):
245 """ Prints a prompt and starts a new editing buffer.
253 """ Prints a prompt and starts a new editing buffer.
246
254
247 Subclasses should use this method to make sure that the
255 Subclasses should use this method to make sure that the
248 terminal is put in a state favorable for a new line
256 terminal is put in a state favorable for a new line
249 input.
257 input.
250 """
258 """
251 self.input_buffer = ''
259 self.input_buffer = ''
252 self.write(prompt)
260 self.write(prompt)
253
261
254
262
255 #--------------------------------------------------------------------------
263 #--------------------------------------------------------------------------
256 # Private API
264 # Private API
257 #--------------------------------------------------------------------------
265 #--------------------------------------------------------------------------
258
266
259 def _on_enter(self):
267 def _on_enter(self):
260 """ Called when the return key is pressed in a line editing
268 """ Called when the return key is pressed in a line editing
261 buffer.
269 buffer.
262 """
270 """
263 current_buffer = self.input_buffer
271 current_buffer = self.input_buffer
264 cleaned_buffer = self.prefilter_input(current_buffer)
272 cleaned_buffer = self.prefilter_input(current_buffer)
265 if self.is_complete(cleaned_buffer):
273 if self.is_complete(cleaned_buffer):
266 self.execute(cleaned_buffer, raw_string=current_buffer)
274 self.execute(cleaned_buffer, raw_string=current_buffer)
267 else:
275 else:
268 self.input_buffer += self._get_indent_string(
276 self.input_buffer += self._get_indent_string(
269 current_buffer[:-1])
277 current_buffer[:-1])
270 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
278 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
271 self.input_buffer += '\t'
279 self.input_buffer += '\t'
272
280
273
281
274 def _get_indent_string(self, string):
282 def _get_indent_string(self, string):
275 """ Return the string of whitespace that prefixes a line. Used to
283 """ Return the string of whitespace that prefixes a line. Used to
276 add the right amount of indendation when creating a new line.
284 add the right amount of indendation when creating a new line.
277 """
285 """
278 string = string.replace('\t', ' '*4)
286 string = string.replace('\t', ' '*4)
279 string = string.split('\n')[-1]
287 string = string.split('\n')[-1]
280 indent_chars = len(string) - len(string.lstrip())
288 indent_chars = len(string) - len(string.lstrip())
281 indent_string = '\t'*(indent_chars // 4) + \
289 indent_string = '\t'*(indent_chars // 4) + \
282 ' '*(indent_chars % 4)
290 ' '*(indent_chars % 4)
283
291
284 return indent_string
292 return indent_string
285
293
286
294
@@ -1,203 +1,208 b''
1 """
1 """
2 Frontend class that uses IPython0 to prefilter the inputs.
2 Frontend class that uses IPython0 to prefilter the inputs.
3
3
4 Using the IPython0 mechanism gives us access to the magics.
4 Using the IPython0 mechanism gives us access to the magics.
5
5
6 This is a transitory class, used here to do the transition between
6 This is a transitory class, used here to do the transition between
7 ipython0 and ipython1. This class is meant to be short-lived as more
7 ipython0 and ipython1. This class is meant to be short-lived as more
8 functionnality is abstracted out of ipython0 in reusable functions and
8 functionnality is abstracted out of ipython0 in reusable functions and
9 is added on the interpreter. This class can be a used to guide this
9 is added on the interpreter. This class can be a used to guide this
10 refactoring.
10 refactoring.
11 """
11 """
12 __docformat__ = "restructuredtext en"
12 __docformat__ = "restructuredtext en"
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20
20
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24 import sys
24 import sys
25
25
26 from linefrontendbase import LineFrontEndBase, common_prefix
26 from linefrontendbase import LineFrontEndBase, common_prefix
27
27
28 from IPython.ipmaker import make_IPython
28 from IPython.ipmaker import make_IPython
29 from IPython.ipapi import IPApi
29 from IPython.ipapi import IPApi
30 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
30 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
31
31
32 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
32 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
33
33
34 from IPython.genutils import Term
34 from IPython.genutils import Term
35 import pydoc
35 import pydoc
36 import os
36 import os
37
37
38
38
39 def mk_system_call(system_call_function, command):
39 def mk_system_call(system_call_function, command):
40 """ given a os.system replacement, and a leading string command,
40 """ given a os.system replacement, and a leading string command,
41 returns a function that will execute the command with the given
41 returns a function that will execute the command with the given
42 argument string.
42 argument string.
43 """
43 """
44 def my_system_call(args):
44 def my_system_call(args):
45 system_call_function("%s %s" % (command, args))
45 system_call_function("%s %s" % (command, args))
46 return my_system_call
46 return my_system_call
47
47
48 #-------------------------------------------------------------------------------
48 #-------------------------------------------------------------------------------
49 # Frontend class using ipython0 to do the prefiltering.
49 # Frontend class using ipython0 to do the prefiltering.
50 #-------------------------------------------------------------------------------
50 #-------------------------------------------------------------------------------
51 class PrefilterFrontEnd(LineFrontEndBase):
51 class PrefilterFrontEnd(LineFrontEndBase):
52 """ Class that uses ipython0 to do prefilter the input, do the
52 """ Class that uses ipython0 to do prefilter the input, do the
53 completion and the magics.
53 completion and the magics.
54
54
55 The core trick is to use an ipython0 instance to prefilter the
55 The core trick is to use an ipython0 instance to prefilter the
56 input, and share the namespace between the interpreter instance used
56 input, and share the namespace between the interpreter instance used
57 to execute the statements and the ipython0 used for code
57 to execute the statements and the ipython0 used for code
58 completion...
58 completion...
59 """
59 """
60
60
61 def __init__(self, *args, **kwargs):
61 def __init__(self, *args, **kwargs):
62 LineFrontEndBase.__init__(self, *args, **kwargs)
63 self.save_output_hooks()
62 self.save_output_hooks()
64 # Instanciate an IPython0 interpreter to be able to use the
63 # Instanciate an IPython0 interpreter to be able to use the
65 # prefiltering.
64 # prefiltering.
66 self.ipython0 = make_IPython()
65 self.ipython0 = make_IPython()
67 # Set the pager:
66 # Set the pager:
68 self.ipython0.set_hook('show_in_pager',
67 self.ipython0.set_hook('show_in_pager',
69 lambda s, string: self.write("\n"+string))
68 lambda s, string: self.write("\n"+string))
70 self.ipython0.write = self.write
69 self.ipython0.write = self.write
71 self._ip = _ip = IPApi(self.ipython0)
70 self._ip = _ip = IPApi(self.ipython0)
72 # XXX: Hack: mix the two namespaces
73 self.shell.user_ns = self.ipython0.user_ns
74 self.shell.user_global_ns = self.ipython0.user_global_ns
75 # Make sure the raw system call doesn't get called, as we don't
71 # Make sure the raw system call doesn't get called, as we don't
76 # have a stdin accessible.
72 # have a stdin accessible.
77 self._ip.system = self.system_call
73 self._ip.system = self.system_call
78 # XXX: Muck around with magics so that they work better
74 # XXX: Muck around with magics so that they work better
79 # in our environment
75 # in our environment
80 self.ipython0.magic_ls = mk_system_call(self.system_call,
76 self.ipython0.magic_ls = mk_system_call(self.system_call,
81 'ls -CF')
77 'ls -CF')
82 # And now clean up the mess created by ipython0
78 # And now clean up the mess created by ipython0
83 self.release_output()
79 self.release_output()
80 if not 'banner' in kwargs:
81 kwargs['banner'] = self.ipython0.BANNER + """
82 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
83
84 LineFrontEndBase.__init__(self, *args, **kwargs)
85 # XXX: Hack: mix the two namespaces
86 self.shell.user_ns = self.ipython0.user_ns
87 self.shell.user_global_ns = self.ipython0.user_global_ns
88
84 self.shell.output_trap = RedirectorOutputTrap(
89 self.shell.output_trap = RedirectorOutputTrap(
85 out_callback=self.write,
90 out_callback=self.write,
86 err_callback=self.write,
91 err_callback=self.write,
87 )
92 )
88 self.shell.traceback_trap = SyncTracebackTrap(
93 self.shell.traceback_trap = SyncTracebackTrap(
89 formatters=self.shell.traceback_trap.formatters,
94 formatters=self.shell.traceback_trap.formatters,
90 )
95 )
91
96
92 #--------------------------------------------------------------------------
97 #--------------------------------------------------------------------------
93 # FrontEndBase interface
98 # FrontEndBase interface
94 #--------------------------------------------------------------------------
99 #--------------------------------------------------------------------------
95
100
96 def show_traceback(self):
101 def show_traceback(self):
97 """ Use ipython0 to capture the last traceback and display it.
102 """ Use ipython0 to capture the last traceback and display it.
98 """
103 """
99 self.capture_output()
104 self.capture_output()
100 self.ipython0.showtraceback()
105 self.ipython0.showtraceback()
101 self.release_output()
106 self.release_output()
102
107
103
108
104 def execute(self, python_string, raw_string=None):
109 def execute(self, python_string, raw_string=None):
105 self.capture_output()
110 self.capture_output()
106 LineFrontEndBase.execute(self, python_string,
111 LineFrontEndBase.execute(self, python_string,
107 raw_string=raw_string)
112 raw_string=raw_string)
108 self.release_output()
113 self.release_output()
109
114
110
115
111 def save_output_hooks(self):
116 def save_output_hooks(self):
112 """ Store all the output hooks we can think of, to be able to
117 """ Store all the output hooks we can think of, to be able to
113 restore them.
118 restore them.
114
119
115 We need to do this early, as starting the ipython0 instance will
120 We need to do this early, as starting the ipython0 instance will
116 screw ouput hooks.
121 screw ouput hooks.
117 """
122 """
118 self.__old_cout_write = Term.cout.write
123 self.__old_cout_write = Term.cout.write
119 self.__old_cerr_write = Term.cerr.write
124 self.__old_cerr_write = Term.cerr.write
120 self.__old_stdout = sys.stdout
125 self.__old_stdout = sys.stdout
121 self.__old_stderr= sys.stderr
126 self.__old_stderr= sys.stderr
122 self.__old_help_output = pydoc.help.output
127 self.__old_help_output = pydoc.help.output
123 self.__old_display_hook = sys.displayhook
128 self.__old_display_hook = sys.displayhook
124
129
125
130
126 def capture_output(self):
131 def capture_output(self):
127 """ Capture all the output mechanisms we can think of.
132 """ Capture all the output mechanisms we can think of.
128 """
133 """
129 self.save_output_hooks()
134 self.save_output_hooks()
130 Term.cout.write = self.write
135 Term.cout.write = self.write
131 Term.cerr.write = self.write
136 Term.cerr.write = self.write
132 sys.stdout = Term.cout
137 sys.stdout = Term.cout
133 sys.stderr = Term.cerr
138 sys.stderr = Term.cerr
134 pydoc.help.output = self.shell.output_trap.out
139 pydoc.help.output = self.shell.output_trap.out
135
140
136
141
137 def release_output(self):
142 def release_output(self):
138 """ Release all the different captures we have made.
143 """ Release all the different captures we have made.
139 """
144 """
140 Term.cout.write = self.__old_cout_write
145 Term.cout.write = self.__old_cout_write
141 Term.cerr.write = self.__old_cerr_write
146 Term.cerr.write = self.__old_cerr_write
142 sys.stdout = self.__old_stdout
147 sys.stdout = self.__old_stdout
143 sys.stderr = self.__old_stderr
148 sys.stderr = self.__old_stderr
144 pydoc.help.output = self.__old_help_output
149 pydoc.help.output = self.__old_help_output
145 sys.displayhook = self.__old_display_hook
150 sys.displayhook = self.__old_display_hook
146
151
147
152
148 def complete(self, line):
153 def complete(self, line):
149 word = line.split('\n')[-1].split(' ')[-1]
154 word = line.split('\n')[-1].split(' ')[-1]
150 completions = self.ipython0.complete(word)
155 completions = self.ipython0.complete(word)
151 # FIXME: The proper sort should be done in the complete method.
156 # FIXME: The proper sort should be done in the complete method.
152 key = lambda x: x.replace('_', '')
157 key = lambda x: x.replace('_', '')
153 completions.sort(key=key)
158 completions.sort(key=key)
154 if completions:
159 if completions:
155 prefix = common_prefix(completions)
160 prefix = common_prefix(completions)
156 line = line[:-len(word)] + prefix
161 line = line[:-len(word)] + prefix
157 return line, completions
162 return line, completions
158
163
159
164
160 #--------------------------------------------------------------------------
165 #--------------------------------------------------------------------------
161 # LineFrontEndBase interface
166 # LineFrontEndBase interface
162 #--------------------------------------------------------------------------
167 #--------------------------------------------------------------------------
163
168
164 def prefilter_input(self, input_string):
169 def prefilter_input(self, input_string):
165 """ Using IPython0 to prefilter the commands to turn them
170 """ Using IPython0 to prefilter the commands to turn them
166 in executable statements that are valid Python strings.
171 in executable statements that are valid Python strings.
167 """
172 """
168 input_string = LineFrontEndBase.prefilter_input(self, input_string)
173 input_string = LineFrontEndBase.prefilter_input(self, input_string)
169 filtered_lines = []
174 filtered_lines = []
170 # The IPython0 prefilters sometime produce output. We need to
175 # The IPython0 prefilters sometime produce output. We need to
171 # capture it.
176 # capture it.
172 self.capture_output()
177 self.capture_output()
173 self.last_result = dict(number=self.prompt_number)
178 self.last_result = dict(number=self.prompt_number)
174 try:
179 try:
175 for line in input_string.split('\n'):
180 for line in input_string.split('\n'):
176 filtered_lines.append(self.ipython0.prefilter(line, False))
181 filtered_lines.append(self.ipython0.prefilter(line, False))
177 except:
182 except:
178 # XXX: probably not the right thing to do.
183 # XXX: probably not the right thing to do.
179 self.ipython0.showsyntaxerror()
184 self.ipython0.showsyntaxerror()
180 self.after_execute()
185 self.after_execute()
181 finally:
186 finally:
182 self.release_output()
187 self.release_output()
183
188
184 filtered_string = '\n'.join(filtered_lines)
189 filtered_string = '\n'.join(filtered_lines)
185 return filtered_string
190 return filtered_string
186
191
187
192
188 #--------------------------------------------------------------------------
193 #--------------------------------------------------------------------------
189 # PrefilterFrontEnd interface
194 # PrefilterFrontEnd interface
190 #--------------------------------------------------------------------------
195 #--------------------------------------------------------------------------
191
196
192 def system_call(self, command_string):
197 def system_call(self, command_string):
193 """ Allows for frontend to define their own system call, to be
198 """ Allows for frontend to define their own system call, to be
194 able capture output and redirect input.
199 able capture output and redirect input.
195 """
200 """
196 return os.system(command_string)
201 return os.system(command_string)
197
202
198
203
199 def do_exit(self):
204 def do_exit(self):
200 """ Exit the shell, cleanup and save the history.
205 """ Exit the shell, cleanup and save the history.
201 """
206 """
202 self.ipython0.atexit_operations()
207 self.ipython0.atexit_operations()
203
208
@@ -1,408 +1,406 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A Wx widget to act as a console and input commands.
3 A Wx widget to act as a console and input commands.
4
4
5 This widget deals with prompts and provides an edit buffer
5 This widget deals with prompts and provides an edit buffer
6 restricted to after the last prompt.
6 restricted to after the last prompt.
7 """
7 """
8
8
9 __docformat__ = "restructuredtext en"
9 __docformat__ = "restructuredtext en"
10
10
11 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
12 # Copyright (C) 2008 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is
14 # Distributed under the terms of the BSD License. The full license is
15 # in the file COPYING, distributed as part of this software.
15 # in the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 import wx
22 import wx
23 import wx.stc as stc
23 import wx.stc as stc
24
24
25 from wx.py import editwindow
25 from wx.py import editwindow
26 import sys
26 import sys
27 LINESEP = '\n'
27 LINESEP = '\n'
28 if sys.platform == 'win32':
28 if sys.platform == 'win32':
29 LINESEP = '\n\r'
29 LINESEP = '\n\r'
30
30
31 import re
31 import re
32
32
33 # FIXME: Need to provide an API for non user-generated display on the
33 # FIXME: Need to provide an API for non user-generated display on the
34 # screen: this should not be editable by the user.
34 # screen: this should not be editable by the user.
35
35
36 _DEFAULT_SIZE = 10
36 _DEFAULT_SIZE = 10
37
37
38 _DEFAULT_STYLE = {
38 _DEFAULT_STYLE = {
39 'stdout' : 'fore:#0000FF',
39 'stdout' : 'fore:#0000FF',
40 'stderr' : 'fore:#007f00',
40 'stderr' : 'fore:#007f00',
41 'trace' : 'fore:#FF0000',
41 'trace' : 'fore:#FF0000',
42
42
43 'default' : 'size:%d' % _DEFAULT_SIZE,
43 'default' : 'size:%d' % _DEFAULT_SIZE,
44 'bracegood' : 'fore:#00AA00,back:#000000,bold',
44 'bracegood' : 'fore:#00AA00,back:#000000,bold',
45 'bracebad' : 'fore:#FF0000,back:#000000,bold',
45 'bracebad' : 'fore:#FF0000,back:#000000,bold',
46
46
47 # properties for the various Python lexer styles
47 # properties for the various Python lexer styles
48 'comment' : 'fore:#007F00',
48 'comment' : 'fore:#007F00',
49 'number' : 'fore:#007F7F',
49 'number' : 'fore:#007F7F',
50 'string' : 'fore:#7F007F,italic',
50 'string' : 'fore:#7F007F,italic',
51 'char' : 'fore:#7F007F,italic',
51 'char' : 'fore:#7F007F,italic',
52 'keyword' : 'fore:#00007F,bold',
52 'keyword' : 'fore:#00007F,bold',
53 'triple' : 'fore:#7F0000',
53 'triple' : 'fore:#7F0000',
54 'tripledouble' : 'fore:#7F0000',
54 'tripledouble' : 'fore:#7F0000',
55 'class' : 'fore:#0000FF,bold,underline',
55 'class' : 'fore:#0000FF,bold,underline',
56 'def' : 'fore:#007F7F,bold',
56 'def' : 'fore:#007F7F,bold',
57 'operator' : 'bold'
57 'operator' : 'bold'
58 }
58 }
59
59
60 # new style numbers
60 # new style numbers
61 _STDOUT_STYLE = 15
61 _STDOUT_STYLE = 15
62 _STDERR_STYLE = 16
62 _STDERR_STYLE = 16
63 _TRACE_STYLE = 17
63 _TRACE_STYLE = 17
64
64
65
65
66 # system colors
66 # system colors
67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
68
68
69 #-------------------------------------------------------------------------------
69 #-------------------------------------------------------------------------------
70 # The console widget class
70 # The console widget class
71 #-------------------------------------------------------------------------------
71 #-------------------------------------------------------------------------------
72 class ConsoleWidget(editwindow.EditWindow):
72 class ConsoleWidget(editwindow.EditWindow):
73 """ Specialized styled text control view for console-like workflow.
73 """ Specialized styled text control view for console-like workflow.
74
74
75 This widget is mainly interested in dealing with the prompt and
75 This widget is mainly interested in dealing with the prompt and
76 keeping the cursor inside the editing line.
76 keeping the cursor inside the editing line.
77 """
77 """
78
78
79 # This is where the title captured from the ANSI escape sequences are
79 # This is where the title captured from the ANSI escape sequences are
80 # stored.
80 # stored.
81 title = 'Console'
81 title = 'Console'
82
82
83 # The buffer being edited.
83 # The buffer being edited.
84 def _set_input_buffer(self, string):
84 def _set_input_buffer(self, string):
85 self.SetSelection(self.current_prompt_pos, self.GetLength())
85 self.SetSelection(self.current_prompt_pos, self.GetLength())
86 self.ReplaceSelection(string)
86 self.ReplaceSelection(string)
87 self.GotoPos(self.GetLength())
87 self.GotoPos(self.GetLength())
88
88
89 def _get_input_buffer(self):
89 def _get_input_buffer(self):
90 """ Returns the text in current edit buffer.
90 """ Returns the text in current edit buffer.
91 """
91 """
92 input_buffer = self.GetTextRange(self.current_prompt_pos,
92 input_buffer = self.GetTextRange(self.current_prompt_pos,
93 self.GetLength())
93 self.GetLength())
94 input_buffer = input_buffer.replace(LINESEP, '\n')
94 input_buffer = input_buffer.replace(LINESEP, '\n')
95 return input_buffer
95 return input_buffer
96
96
97 input_buffer = property(_get_input_buffer, _set_input_buffer)
97 input_buffer = property(_get_input_buffer, _set_input_buffer)
98
98
99 style = _DEFAULT_STYLE.copy()
99 style = _DEFAULT_STYLE.copy()
100
100
101 # Translation table from ANSI escape sequences to color. Override
101 # Translation table from ANSI escape sequences to color. Override
102 # this to specify your colors.
102 # this to specify your colors.
103 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
103 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
104 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
104 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
105 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
105 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
106 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
106 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
107 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
107 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
108 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
108 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
109 '1;34': [12, 'LIGHT BLUE'], '1;35':
109 '1;34': [12, 'LIGHT BLUE'], '1;35':
110 [13, 'MEDIUM VIOLET RED'],
110 [13, 'MEDIUM VIOLET RED'],
111 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
111 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
112
112
113 # The color of the carret (call _apply_style() after setting)
113 # The color of the carret (call _apply_style() after setting)
114 carret_color = 'BLACK'
114 carret_color = 'BLACK'
115
115
116 #--------------------------------------------------------------------------
116 #--------------------------------------------------------------------------
117 # Public API
117 # Public API
118 #--------------------------------------------------------------------------
118 #--------------------------------------------------------------------------
119
119
120 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
120 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
121 size=wx.DefaultSize, style=0, ):
121 size=wx.DefaultSize, style=0, ):
122 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
122 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
123 self._configure_scintilla()
123 self._configure_scintilla()
124
124
125 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
125 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
126 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
126 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
127
127
128
128
129 def write(self, text, refresh=True):
129 def write(self, text, refresh=True):
130 """ Write given text to buffer, while translating the ansi escape
130 """ Write given text to buffer, while translating the ansi escape
131 sequences.
131 sequences.
132 """
132 """
133 # XXX: do not put print statements to sys.stdout/sys.stderr in
133 # XXX: do not put print statements to sys.stdout/sys.stderr in
134 # this method, the print statements will call this method, as
134 # this method, the print statements will call this method, as
135 # you will end up with an infinit loop
135 # you will end up with an infinit loop
136 title = self.title_pat.split(text)
136 title = self.title_pat.split(text)
137 if len(title)>1:
137 if len(title)>1:
138 self.title = title[-2]
138 self.title = title[-2]
139
139
140 text = self.title_pat.sub('', text)
140 text = self.title_pat.sub('', text)
141 segments = self.color_pat.split(text)
141 segments = self.color_pat.split(text)
142 segment = segments.pop(0)
142 segment = segments.pop(0)
143 self.GotoPos(self.GetLength())
143 self.GotoPos(self.GetLength())
144 self.StartStyling(self.GetLength(), 0xFF)
144 self.StartStyling(self.GetLength(), 0xFF)
145 try:
145 try:
146 self.AppendText(segment)
146 self.AppendText(segment)
147 except UnicodeDecodeError:
147 except UnicodeDecodeError:
148 # XXX: Do I really want to skip the exception?
148 # XXX: Do I really want to skip the exception?
149 pass
149 pass
150
150
151 if segments:
151 if segments:
152 for ansi_tag, text in zip(segments[::2], segments[1::2]):
152 for ansi_tag, text in zip(segments[::2], segments[1::2]):
153 self.StartStyling(self.GetLength(), 0xFF)
153 self.StartStyling(self.GetLength(), 0xFF)
154 try:
154 try:
155 self.AppendText(text)
155 self.AppendText(text)
156 except UnicodeDecodeError:
156 except UnicodeDecodeError:
157 # XXX: Do I really want to skip the exception?
157 # XXX: Do I really want to skip the exception?
158 pass
158 pass
159
159
160 if ansi_tag not in self.ANSI_STYLES:
160 if ansi_tag not in self.ANSI_STYLES:
161 style = 0
161 style = 0
162 else:
162 else:
163 style = self.ANSI_STYLES[ansi_tag][0]
163 style = self.ANSI_STYLES[ansi_tag][0]
164
164
165 self.SetStyling(len(text), style)
165 self.SetStyling(len(text), style)
166
166
167 self.GotoPos(self.GetLength())
167 self.GotoPos(self.GetLength())
168 if refresh:
168 if refresh:
169 wx.Yield()
169 wx.Yield()
170
170
171
171
172 def new_prompt(self, prompt):
172 def new_prompt(self, prompt):
173 """ Prints a prompt at start of line, and move the start of the
173 """ Prints a prompt at start of line, and move the start of the
174 current block there.
174 current block there.
175
175
176 The prompt can be given with ascii escape sequences.
176 The prompt can be given with ascii escape sequences.
177 """
177 """
178 self.write(prompt)
178 self.write(prompt, refresh=False)
179 # now we update our cursor giving end of prompt
179 # now we update our cursor giving end of prompt
180 self.current_prompt_pos = self.GetLength()
180 self.current_prompt_pos = self.GetLength()
181 self.current_prompt_line = self.GetCurrentLine()
181 self.current_prompt_line = self.GetCurrentLine()
182 wx.Yield()
182 wx.Yield()
183 self.EnsureCaretVisible()
183 self.EnsureCaretVisible()
184
184
185
185
186 def scroll_to_bottom(self):
186 def scroll_to_bottom(self):
187 maxrange = self.GetScrollRange(wx.VERTICAL)
187 maxrange = self.GetScrollRange(wx.VERTICAL)
188 self.ScrollLines(maxrange)
188 self.ScrollLines(maxrange)
189
189
190
190
191 def pop_completion(self, possibilities, offset=0):
191 def pop_completion(self, possibilities, offset=0):
192 """ Pops up an autocompletion menu. Offset is the offset
192 """ Pops up an autocompletion menu. Offset is the offset
193 in characters of the position at which the menu should
193 in characters of the position at which the menu should
194 appear, relativ to the cursor.
194 appear, relativ to the cursor.
195 """
195 """
196 self.AutoCompSetIgnoreCase(False)
196 self.AutoCompSetIgnoreCase(False)
197 self.AutoCompSetAutoHide(False)
197 self.AutoCompSetAutoHide(False)
198 self.AutoCompSetMaxHeight(len(possibilities))
198 self.AutoCompSetMaxHeight(len(possibilities))
199 self.AutoCompShow(offset, " ".join(possibilities))
199 self.AutoCompShow(offset, " ".join(possibilities))
200
200
201
201
202 def get_line_width(self):
202 def get_line_width(self):
203 """ Return the width of the line in characters.
203 """ Return the width of the line in characters.
204 """
204 """
205 return self.GetSize()[0]/self.GetCharWidth()
205 return self.GetSize()[0]/self.GetCharWidth()
206
206
207
207
208 #--------------------------------------------------------------------------
208 #--------------------------------------------------------------------------
209 # Private API
209 # Private API
210 #--------------------------------------------------------------------------
210 #--------------------------------------------------------------------------
211
211
212 def _apply_style(self):
212 def _apply_style(self):
213 """ Applies the colors for the different text elements and the
213 """ Applies the colors for the different text elements and the
214 carret.
214 carret.
215 """
215 """
216 self.SetCaretForeground(self.carret_color)
216 self.SetCaretForeground(self.carret_color)
217
217
218 #self.StyleClearAll()
218 #self.StyleClearAll()
219 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
219 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
220 "fore:#FF0000,back:#0000FF,bold")
220 "fore:#FF0000,back:#0000FF,bold")
221 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
221 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
222 "fore:#000000,back:#FF0000,bold")
222 "fore:#000000,back:#FF0000,bold")
223
223
224 for style in self.ANSI_STYLES.values():
224 for style in self.ANSI_STYLES.values():
225 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
225 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
226
226
227
227
228 def _configure_scintilla(self):
228 def _configure_scintilla(self):
229 self.SetEOLMode(stc.STC_EOL_LF)
229 self.SetEOLMode(stc.STC_EOL_LF)
230
230
231 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
231 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
232 # the widget
232 # the widget
233 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
233 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
234 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
234 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
235 # Also allow Ctrl Shift "=" for poor non US keyboard users.
235 # Also allow Ctrl Shift "=" for poor non US keyboard users.
236 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
236 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
237 stc.STC_CMD_ZOOMIN)
237 stc.STC_CMD_ZOOMIN)
238
238
239 # Keys: we need to clear some of the keys the that don't play
239 # Keys: we need to clear some of the keys the that don't play
240 # well with a console.
240 # well with a console.
241 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
241 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
242 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
242 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
243 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
243 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
244
244
245 self.SetEOLMode(stc.STC_EOL_CRLF)
245 self.SetEOLMode(stc.STC_EOL_CRLF)
246 self.SetWrapMode(stc.STC_WRAP_CHAR)
246 self.SetWrapMode(stc.STC_WRAP_CHAR)
247 self.SetWrapMode(stc.STC_WRAP_WORD)
247 self.SetWrapMode(stc.STC_WRAP_WORD)
248 self.SetBufferedDraw(True)
248 self.SetBufferedDraw(True)
249 self.SetUseAntiAliasing(True)
249 self.SetUseAntiAliasing(True)
250 self.SetLayoutCache(stc.STC_CACHE_PAGE)
250 self.SetLayoutCache(stc.STC_CACHE_PAGE)
251 self.SetUndoCollection(False)
251 self.SetUndoCollection(False)
252 self.SetUseTabs(True)
252 self.SetUseTabs(True)
253 self.SetIndent(4)
253 self.SetIndent(4)
254 self.SetTabWidth(4)
254 self.SetTabWidth(4)
255
255
256 self.EnsureCaretVisible()
257 # we don't want scintilla's autocompletion to choose
256 # we don't want scintilla's autocompletion to choose
258 # automaticaly out of a single choice list, as we pop it up
257 # automaticaly out of a single choice list, as we pop it up
259 # automaticaly
258 # automaticaly
260 self.AutoCompSetChooseSingle(False)
259 self.AutoCompSetChooseSingle(False)
261 self.AutoCompSetMaxHeight(10)
260 self.AutoCompSetMaxHeight(10)
262 # XXX: this doesn't seem to have an effect.
261 # XXX: this doesn't seem to have an effect.
263 self.AutoCompSetFillUps('\n')
262 self.AutoCompSetFillUps('\n')
264
263
265 self.SetMargins(3, 3) #text is moved away from border with 3px
264 self.SetMargins(3, 3) #text is moved away from border with 3px
266 # Suppressing Scintilla margins
265 # Suppressing Scintilla margins
267 self.SetMarginWidth(0, 0)
266 self.SetMarginWidth(0, 0)
268 self.SetMarginWidth(1, 0)
267 self.SetMarginWidth(1, 0)
269 self.SetMarginWidth(2, 0)
268 self.SetMarginWidth(2, 0)
270
269
271 self._apply_style()
270 self._apply_style()
272
271
273 # Xterm escape sequences
272 # Xterm escape sequences
274 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
273 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
275 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
274 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
276
275
277 #self.SetEdgeMode(stc.STC_EDGE_LINE)
276 #self.SetEdgeMode(stc.STC_EDGE_LINE)
278 #self.SetEdgeColumn(80)
277 #self.SetEdgeColumn(80)
279
278
280 # styles
279 # styles
281 p = self.style
280 p = self.style
282 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
281 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
283 self.StyleClearAll()
282 self.StyleClearAll()
284 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
283 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
285 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
284 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
286 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
285 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
287
286
288 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
287 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
289 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
288 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
290 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
289 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
291 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
290 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
292 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
291 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
293 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
292 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
294 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
293 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
295 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
294 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
296 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
295 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
297 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
296 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
298 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
297 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
299 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
298 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
300 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
299 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
301 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
300 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
302
301
303
304 def _on_key_down(self, event, skip=True):
302 def _on_key_down(self, event, skip=True):
305 """ Key press callback used for correcting behavior for
303 """ Key press callback used for correcting behavior for
306 console-like interfaces: the cursor is constraint to be after
304 console-like interfaces: the cursor is constraint to be after
307 the last prompt.
305 the last prompt.
308
306
309 Return True if event as been catched.
307 Return True if event as been catched.
310 """
308 """
311 catched = True
309 catched = True
312 # Intercept some specific keys.
310 # Intercept some specific keys.
313 if event.KeyCode == ord('L') and event.ControlDown() :
311 if event.KeyCode == ord('L') and event.ControlDown() :
314 self.scroll_to_bottom()
312 self.scroll_to_bottom()
315 elif event.KeyCode == ord('K') and event.ControlDown() :
313 elif event.KeyCode == ord('K') and event.ControlDown() :
316 self.input_buffer = ''
314 self.input_buffer = ''
317 elif event.KeyCode == wx.WXK_PAGEUP:
315 elif event.KeyCode == wx.WXK_PAGEUP:
318 self.ScrollPages(-1)
316 self.ScrollPages(-1)
319 elif event.KeyCode == wx.WXK_PAGEDOWN:
317 elif event.KeyCode == wx.WXK_PAGEDOWN:
320 self.ScrollPages(1)
318 self.ScrollPages(1)
321 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
319 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
322 self.ScrollLines(-1)
320 self.ScrollLines(-1)
323 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
321 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
324 self.ScrollLines(1)
322 self.ScrollLines(1)
325 else:
323 else:
326 catched = False
324 catched = False
327
325
328 if self.AutoCompActive():
326 if self.AutoCompActive():
329 event.Skip()
327 event.Skip()
330 else:
328 else:
331 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
329 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
332 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
330 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
333 catched = True
331 catched = True
334 self.CallTipCancel()
332 self.CallTipCancel()
335 self.write('\n', refresh=False)
333 self.write('\n', refresh=False)
336 # Under windows scintilla seems to be doing funny stuff to the
334 # Under windows scintilla seems to be doing funny stuff to the
337 # line returns here, but the getter for input_buffer filters
335 # line returns here, but the getter for input_buffer filters
338 # this out.
336 # this out.
339 if sys.platform == 'win32':
337 if sys.platform == 'win32':
340 self.input_buffer = self.input_buffer
338 self.input_buffer = self.input_buffer
341 self._on_enter()
339 self._on_enter()
342
340
343 elif event.KeyCode == wx.WXK_HOME:
341 elif event.KeyCode == wx.WXK_HOME:
344 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
342 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
345 self.GotoPos(self.current_prompt_pos)
343 self.GotoPos(self.current_prompt_pos)
346 catched = True
344 catched = True
347
345
348 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
346 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
349 # FIXME: This behavior is not ideal: if the selection
347 # FIXME: This behavior is not ideal: if the selection
350 # is already started, it will jump.
348 # is already started, it will jump.
351 self.SetSelectionStart(self.current_prompt_pos)
349 self.SetSelectionStart(self.current_prompt_pos)
352 self.SetSelectionEnd(self.GetCurrentPos())
350 self.SetSelectionEnd(self.GetCurrentPos())
353 catched = True
351 catched = True
354
352
355 elif event.KeyCode == wx.WXK_UP:
353 elif event.KeyCode == wx.WXK_UP:
356 if self.GetCurrentLine() > self.current_prompt_line:
354 if self.GetCurrentLine() > self.current_prompt_line:
357 if self.GetCurrentLine() == self.current_prompt_line + 1 \
355 if self.GetCurrentLine() == self.current_prompt_line + 1 \
358 and self.GetColumn(self.GetCurrentPos()) < \
356 and self.GetColumn(self.GetCurrentPos()) < \
359 self.GetColumn(self.current_prompt_pos):
357 self.GetColumn(self.current_prompt_pos):
360 self.GotoPos(self.current_prompt_pos)
358 self.GotoPos(self.current_prompt_pos)
361 else:
359 else:
362 event.Skip()
360 event.Skip()
363 catched = True
361 catched = True
364
362
365 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
363 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
366 if self.GetCurrentPos() > self.current_prompt_pos:
364 if self.GetCurrentPos() > self.current_prompt_pos:
367 event.Skip()
365 event.Skip()
368 catched = True
366 catched = True
369
367
370 if skip and not catched:
368 if skip and not catched:
371 # Put the cursor back in the edit region
369 # Put the cursor back in the edit region
372 if self.GetCurrentPos() < self.current_prompt_pos:
370 if self.GetCurrentPos() < self.current_prompt_pos:
373 self.GotoPos(self.current_prompt_pos)
371 self.GotoPos(self.current_prompt_pos)
374 else:
372 else:
375 event.Skip()
373 event.Skip()
376
374
377 return catched
375 return catched
378
376
379
377
380 def _on_key_up(self, event, skip=True):
378 def _on_key_up(self, event, skip=True):
381 """ If cursor is outside the editing region, put it back.
379 """ If cursor is outside the editing region, put it back.
382 """
380 """
383 event.Skip()
381 event.Skip()
384 if self.GetCurrentPos() < self.current_prompt_pos:
382 if self.GetCurrentPos() < self.current_prompt_pos:
385 self.GotoPos(self.current_prompt_pos)
383 self.GotoPos(self.current_prompt_pos)
386
384
387
385
388
386
389 if __name__ == '__main__':
387 if __name__ == '__main__':
390 # Some simple code to test the console widget.
388 # Some simple code to test the console widget.
391 class MainWindow(wx.Frame):
389 class MainWindow(wx.Frame):
392 def __init__(self, parent, id, title):
390 def __init__(self, parent, id, title):
393 wx.Frame.__init__(self, parent, id, title, size=(300,250))
391 wx.Frame.__init__(self, parent, id, title, size=(300,250))
394 self._sizer = wx.BoxSizer(wx.VERTICAL)
392 self._sizer = wx.BoxSizer(wx.VERTICAL)
395 self.console_widget = ConsoleWidget(self)
393 self.console_widget = ConsoleWidget(self)
396 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
394 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
397 self.SetSizer(self._sizer)
395 self.SetSizer(self._sizer)
398 self.SetAutoLayout(1)
396 self.SetAutoLayout(1)
399 self.Show(True)
397 self.Show(True)
400
398
401 app = wx.PySimpleApp()
399 app = wx.PySimpleApp()
402 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
400 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
403 w.SetSize((780, 460))
401 w.SetSize((780, 460))
404 w.Show()
402 w.Show()
405
403
406 app.MainLoop()
404 app.MainLoop()
407
405
408
406
@@ -1,103 +1,107 b''
1 """
1 """
2 Entry point for a simple application giving a graphical frontend to
2 Entry point for a simple application giving a graphical frontend to
3 ipython.
3 ipython.
4 """
4 """
5
5
6 try:
6 try:
7 import wx
7 import wx
8 except ImportError, e:
8 except ImportError, e:
9 e.message = """%s
9 e.message = """%s
10 ________________________________________________________________________________
10 ________________________________________________________________________________
11 You need wxPython to run this application.
11 You need wxPython to run this application.
12 """ % e.message
12 """ % e.message
13 e.args = (e.message, ) + e.args[1:]
13 e.args = (e.message, ) + e.args[1:]
14 raise e
14 raise e
15
15
16 from wx_frontend import WxController
16 from wx_frontend import WxController
17 import __builtin__
17 import __builtin__
18
18
19
19 class IPythonXController(WxController):
20 class IPythonXController(WxController):
20 """ Sub class of WxController that adds some application-specific
21 """ Sub class of WxController that adds some application-specific
21 bindings.
22 bindings.
22 """
23 """
23
24
24 debug = False
25 debug = False
25
26
26 def __init__(self, *args, **kwargs):
27 def __init__(self, *args, **kwargs):
27 WxController.__init__(self, *args, **kwargs)
28 WxController.__init__(self, *args, **kwargs)
28 self.ipython0.ask_exit = self.do_exit
29 self.ipython0.ask_exit = self.do_exit
30 # Scroll to top
31 maxrange = self.GetScrollRange(wx.VERTICAL)
32 self.ScrollLines(-maxrange)
29
33
30
34
31 def _on_key_down(self, event, skip=True):
35 def _on_key_down(self, event, skip=True):
32 # Intercept Ctrl-D to quit
36 # Intercept Ctrl-D to quit
33 if event.KeyCode == ord('D') and event.ControlDown() and \
37 if event.KeyCode == ord('D') and event.ControlDown() and \
34 self.input_buffer == '' and \
38 self.input_buffer == '' and \
35 self._input_state == 'readline':
39 self._input_state == 'readline':
36 wx.CallAfter(self.ask_exit)
40 wx.CallAfter(self.ask_exit)
37 else:
41 else:
38 WxController._on_key_down(self, event, skip=skip)
42 WxController._on_key_down(self, event, skip=skip)
39
43
40
44
41 def ask_exit(self):
45 def ask_exit(self):
42 """ Ask the user whether to exit.
46 """ Ask the user whether to exit.
43 """
47 """
44 self._input_state = 'subprocess'
48 self._input_state = 'subprocess'
45 self.write('\n', refresh=False)
49 self.write('\n', refresh=False)
46 self.capture_output()
50 self.capture_output()
47 self.ipython0.shell.exit()
51 self.ipython0.shell.exit()
48 self.release_output()
52 self.release_output()
49 if not self.ipython0.exit_now:
53 if not self.ipython0.exit_now:
50 wx.CallAfter(self.new_prompt,
54 wx.CallAfter(self.new_prompt,
51 self.input_prompt_template.substitute(
55 self.input_prompt_template.substitute(
52 number=self.last_result['number'] + 1))
56 number=self.last_result['number'] + 1))
53
57
54
58
55 def do_exit(self):
59 def do_exit(self):
56 """ Exits the interpreter, kills the windows.
60 """ Exits the interpreter, kills the windows.
57 """
61 """
58 WxController.do_exit(self)
62 WxController.do_exit(self)
59 self.release_output()
63 self.release_output()
60 wx.CallAfter(wx.Exit)
64 wx.CallAfter(wx.Exit)
61
65
62
66
63
67
64 class IPythonX(wx.Frame):
68 class IPythonX(wx.Frame):
65 """ Main frame of the IPythonX app.
69 """ Main frame of the IPythonX app.
66 """
70 """
67
71
68 def __init__(self, parent, id, title, debug=False):
72 def __init__(self, parent, id, title, debug=False):
69 wx.Frame.__init__(self, parent, id, title, size=(300,250))
73 wx.Frame.__init__(self, parent, id, title, size=(300,250))
70 self._sizer = wx.BoxSizer(wx.VERTICAL)
74 self._sizer = wx.BoxSizer(wx.VERTICAL)
71 self.shell = IPythonXController(self, debug=debug)
75 self.shell = IPythonXController(self, debug=debug)
72 self._sizer.Add(self.shell, 1, wx.EXPAND)
76 self._sizer.Add(self.shell, 1, wx.EXPAND)
73 self.SetSizer(self._sizer)
77 self.SetSizer(self._sizer)
74 self.SetAutoLayout(1)
78 self.SetAutoLayout(1)
75 self.Show(True)
79 self.Show(True)
76
80
77
81
78 def main():
82 def main():
79 from optparse import OptionParser
83 from optparse import OptionParser
80 usage = """usage: %prog [options]
84 usage = """usage: %prog [options]
81
85
82 Simple graphical frontend to IPython, using WxWidgets."""
86 Simple graphical frontend to IPython, using WxWidgets."""
83 parser = OptionParser(usage=usage)
87 parser = OptionParser(usage=usage)
84 parser.add_option("-d", "--debug",
88 parser.add_option("-d", "--debug",
85 action="store_true", dest="debug", default=False,
89 action="store_true", dest="debug", default=False,
86 help="Enable debug message for the wx frontend.")
90 help="Enable debug message for the wx frontend.")
87
91
88 options, args = parser.parse_args()
92 options, args = parser.parse_args()
89
93
90 # Clear the options, to avoid having the ipython0 instance complain
94 # Clear the options, to avoid having the ipython0 instance complain
91 import sys
95 import sys
92 sys.argv = sys.argv[:1]
96 sys.argv = sys.argv[:1]
93
97
94 app = wx.PySimpleApp()
98 app = wx.PySimpleApp()
95 frame = IPythonX(None, wx.ID_ANY, 'IPythonX', debug=options.debug)
99 frame = IPythonX(None, wx.ID_ANY, 'IPythonX', debug=options.debug)
96 frame.shell.SetFocus()
100 frame.shell.SetFocus()
97 frame.shell.app = app
101 frame.shell.app = app
98 frame.SetSize((680, 460))
102 frame.SetSize((680, 460))
99
103
100 app.MainLoop()
104 app.MainLoop()
101
105
102 if __name__ == '__main__':
106 if __name__ == '__main__':
103 main()
107 main()
General Comments 0
You need to be logged in to leave comments. Login now