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