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