##// END OF EJS Templates
History is now working. + misc keybindings.
Gael Varoquaux -
Show More
@@ -1,351 +1,351 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
3 """
4 frontendbase provides an interface and base class for GUI frontends for
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
5 IPython.kernel/IPython.kernel.core.
6
6
7 Frontend implementations will likely want to subclass FrontEndBase.
7 Frontend implementations will likely want to subclass FrontEndBase.
8
8
9 Author: Barry Wark
9 Author: Barry Wark
10 """
10 """
11 __docformat__ = "restructuredtext en"
11 __docformat__ = "restructuredtext en"
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import string
23 import string
24 import uuid
24 import uuid
25 import _ast
25 import _ast
26
26
27 try:
27 try:
28 from zope.interface import Interface, Attribute, implements, classProvides
28 from zope.interface import Interface, Attribute, implements, classProvides
29 except ImportError:
29 except ImportError:
30 #zope.interface is not available
30 #zope.interface is not available
31 Interface = object
31 Interface = object
32 def Attribute(name, doc): pass
32 def Attribute(name, doc): pass
33 def implements(interface): pass
33 def implements(interface): pass
34 def classProvides(interface): pass
34 def classProvides(interface): pass
35
35
36 from IPython.kernel.core.history import FrontEndHistory
36 from IPython.kernel.core.history import FrontEndHistory
37 from IPython.kernel.core.util import Bunch
37 from IPython.kernel.core.util import Bunch
38
38
39 ##############################################################################
39 ##############################################################################
40 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
40 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
41 # not
41 # not
42
42
43 rc = Bunch()
43 rc = Bunch()
44 rc.prompt_in1 = r'In [$number]: '
44 rc.prompt_in1 = r'In [$number]: '
45 rc.prompt_in2 = r'...'
45 rc.prompt_in2 = r'...'
46 rc.prompt_out = r'Out [$number]: '
46 rc.prompt_out = r'Out [$number]: '
47
47
48 ##############################################################################
48 ##############################################################################
49
49
50 class IFrontEndFactory(Interface):
50 class IFrontEndFactory(Interface):
51 """Factory interface for frontends."""
51 """Factory interface for frontends."""
52
52
53 def __call__(engine=None, history=None):
53 def __call__(engine=None, history=None):
54 """
54 """
55 Parameters:
55 Parameters:
56 interpreter : IPython.kernel.engineservice.IEngineCore
56 interpreter : IPython.kernel.engineservice.IEngineCore
57 """
57 """
58
58
59 pass
59 pass
60
60
61
61
62
62
63 class IFrontEnd(Interface):
63 class IFrontEnd(Interface):
64 """Interface for frontends. All methods return t.i.d.Deferred"""
64 """Interface for frontends. All methods return t.i.d.Deferred"""
65
65
66 Attribute("input_prompt_template", "string.Template instance\
66 Attribute("input_prompt_template", "string.Template instance\
67 substituteable with execute result.")
67 substituteable with execute result.")
68 Attribute("output_prompt_template", "string.Template instance\
68 Attribute("output_prompt_template", "string.Template instance\
69 substituteable with execute result.")
69 substituteable with execute result.")
70 Attribute("continuation_prompt_template", "string.Template instance\
70 Attribute("continuation_prompt_template", "string.Template instance\
71 substituteable with execute result.")
71 substituteable with execute result.")
72
72
73 def update_cell_prompt(result, blockID=None):
73 def update_cell_prompt(result, blockID=None):
74 """Subclass may override to update the input prompt for a block.
74 """Subclass may override to update the input prompt for a block.
75 Since this method will be called as a
75 Since this method will be called as a
76 twisted.internet.defer.Deferred's callback/errback,
76 twisted.internet.defer.Deferred's callback/errback,
77 implementations should return result when finished.
77 implementations should return result when finished.
78
78
79 Result is a result dict in case of success, and a
79 Result is a result dict in case of success, and a
80 twisted.python.util.failure.Failure in case of an error
80 twisted.python.util.failure.Failure in case of an error
81 """
81 """
82
82
83 pass
83 pass
84
84
85
85
86 def render_result(result):
86 def render_result(result):
87 """Render the result of an execute call. Implementors may choose the
87 """Render the result of an execute call. Implementors may choose the
88 method of rendering.
88 method of rendering.
89 For example, a notebook-style frontend might render a Chaco plot
89 For example, a notebook-style frontend might render a Chaco plot
90 inline.
90 inline.
91
91
92 Parameters:
92 Parameters:
93 result : dict (result of IEngineBase.execute )
93 result : dict (result of IEngineBase.execute )
94 blockID = result['blockID']
94 blockID = result['blockID']
95
95
96 Result:
96 Result:
97 Output of frontend rendering
97 Output of frontend rendering
98 """
98 """
99
99
100 pass
100 pass
101
101
102 def render_error(failure):
102 def render_error(failure):
103 """Subclasses must override to render the failure. Since this method
103 """Subclasses must override to render the failure. Since this method
104 will be called as a twisted.internet.defer.Deferred's callback,
104 will be called as a twisted.internet.defer.Deferred's callback,
105 implementations should return result when finished.
105 implementations should return result when finished.
106
106
107 blockID = failure.blockID
107 blockID = failure.blockID
108 """
108 """
109
109
110 pass
110 pass
111
111
112
112
113 def input_prompt(number=''):
113 def input_prompt(number=''):
114 """Returns the input prompt by subsituting into
114 """Returns the input prompt by subsituting into
115 self.input_prompt_template
115 self.input_prompt_template
116 """
116 """
117 pass
117 pass
118
118
119 def output_prompt(number=''):
119 def output_prompt(number=''):
120 """Returns the output prompt by subsituting into
120 """Returns the output prompt by subsituting into
121 self.output_prompt_template
121 self.output_prompt_template
122 """
122 """
123
123
124 pass
124 pass
125
125
126 def continuation_prompt():
126 def continuation_prompt():
127 """Returns the continuation prompt by subsituting into
127 """Returns the continuation prompt by subsituting into
128 self.continuation_prompt_template
128 self.continuation_prompt_template
129 """
129 """
130
130
131 pass
131 pass
132
132
133 def is_complete(block):
133 def is_complete(block):
134 """Returns True if block is complete, False otherwise."""
134 """Returns True if block is complete, False otherwise."""
135
135
136 pass
136 pass
137
137
138 def compile_ast(block):
138 def compile_ast(block):
139 """Compiles block to an _ast.AST"""
139 """Compiles block to an _ast.AST"""
140
140
141 pass
141 pass
142
142
143
143
144 def get_history_previous(currentBlock):
144 def get_history_previous(current_block):
145 """Returns the block previous in the history. Saves currentBlock if
145 """Returns the block previous in the history. Saves currentBlock if
146 the history_cursor is currently at the end of the input history"""
146 the history_cursor is currently at the end of the input history"""
147 pass
147 pass
148
148
149 def get_history_next():
149 def get_history_next():
150 """Returns the next block in the history."""
150 """Returns the next block in the history."""
151
151
152 pass
152 pass
153
153
154
154
155 class FrontEndBase(object):
155 class FrontEndBase(object):
156 """
156 """
157 FrontEndBase manages the state tasks for a CLI frontend:
157 FrontEndBase manages the state tasks for a CLI frontend:
158 - Input and output history management
158 - Input and output history management
159 - Input/continuation and output prompt generation
159 - Input/continuation and output prompt generation
160
160
161 Some issues (due to possibly unavailable engine):
161 Some issues (due to possibly unavailable engine):
162 - How do we get the current cell number for the engine?
162 - How do we get the current cell number for the engine?
163 - How do we handle completions?
163 - How do we handle completions?
164 """
164 """
165
165
166 history_cursor = 0
166 history_cursor = 0
167
167
168 current_indent_level = 0
168 current_indent_level = 0
169
169
170
170
171 input_prompt_template = string.Template(rc.prompt_in1)
171 input_prompt_template = string.Template(rc.prompt_in1)
172 output_prompt_template = string.Template(rc.prompt_out)
172 output_prompt_template = string.Template(rc.prompt_out)
173 continuation_prompt_template = string.Template(rc.prompt_in2)
173 continuation_prompt_template = string.Template(rc.prompt_in2)
174
174
175 def __init__(self, shell=None, history=None):
175 def __init__(self, shell=None, history=None):
176 self.shell = shell
176 self.shell = shell
177 if history is None:
177 if history is None:
178 self.history = FrontEndHistory(input_cache=[''])
178 self.history = FrontEndHistory(input_cache=[''])
179 else:
179 else:
180 self.history = history
180 self.history = history
181
181
182
182
183 def input_prompt(self, number=''):
183 def input_prompt(self, number=''):
184 """Returns the current input prompt
184 """Returns the current input prompt
185
185
186 It would be great to use ipython1.core.prompts.Prompt1 here
186 It would be great to use ipython1.core.prompts.Prompt1 here
187 """
187 """
188 return self.input_prompt_template.safe_substitute({'number':number})
188 return self.input_prompt_template.safe_substitute({'number':number})
189
189
190
190
191 def continuation_prompt(self):
191 def continuation_prompt(self):
192 """Returns the current continuation prompt"""
192 """Returns the current continuation prompt"""
193
193
194 return self.continuation_prompt_template.safe_substitute()
194 return self.continuation_prompt_template.safe_substitute()
195
195
196 def output_prompt(self, number=''):
196 def output_prompt(self, number=''):
197 """Returns the output prompt for result"""
197 """Returns the output prompt for result"""
198
198
199 return self.output_prompt_template.safe_substitute({'number':number})
199 return self.output_prompt_template.safe_substitute({'number':number})
200
200
201
201
202 def is_complete(self, block):
202 def is_complete(self, block):
203 """Determine if block is complete.
203 """Determine if block is complete.
204
204
205 Parameters
205 Parameters
206 block : string
206 block : string
207
207
208 Result
208 Result
209 True if block can be sent to the engine without compile errors.
209 True if block can be sent to the engine without compile errors.
210 False otherwise.
210 False otherwise.
211 """
211 """
212
212
213 try:
213 try:
214 ast = self.compile_ast(block)
214 ast = self.compile_ast(block)
215 except:
215 except:
216 return False
216 return False
217
217
218 lines = block.split('\n')
218 lines = block.split('\n')
219 return (len(lines)==1 or str(lines[-1])=='')
219 return (len(lines)==1 or str(lines[-1])=='')
220
220
221
221
222 def compile_ast(self, block):
222 def compile_ast(self, block):
223 """Compile block to an AST
223 """Compile block to an AST
224
224
225 Parameters:
225 Parameters:
226 block : str
226 block : str
227
227
228 Result:
228 Result:
229 AST
229 AST
230
230
231 Throws:
231 Throws:
232 Exception if block cannot be compiled
232 Exception if block cannot be compiled
233 """
233 """
234
234
235 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
235 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
236
236
237
237
238 def execute(self, block, blockID=None):
238 def execute(self, block, blockID=None):
239 """Execute the block and return the result.
239 """Execute the block and return the result.
240
240
241 Parameters:
241 Parameters:
242 block : {str, AST}
242 block : {str, AST}
243 blockID : any
243 blockID : any
244 Caller may provide an ID to identify this block.
244 Caller may provide an ID to identify this block.
245 result['blockID'] := blockID
245 result['blockID'] := blockID
246
246
247 Result:
247 Result:
248 Deferred result of self.interpreter.execute
248 Deferred result of self.interpreter.execute
249 """
249 """
250
250
251 if(not self.is_complete(block)):
251 if(not self.is_complete(block)):
252 raise Exception("Block is not compilable")
252 raise Exception("Block is not compilable")
253
253
254 if(blockID == None):
254 if(blockID == None):
255 blockID = uuid.uuid4() #random UUID
255 blockID = uuid.uuid4() #random UUID
256
256
257 try:
257 try:
258 result = self.shell.execute(block)
258 result = self.shell.execute(block)
259 except Exception,e:
259 except Exception,e:
260 e = self._add_block_id_for_failure(e, blockID=blockID)
260 e = self._add_block_id_for_failure(e, blockID=blockID)
261 e = self.update_cell_prompt(e, blockID=blockID)
261 e = self.update_cell_prompt(e, blockID=blockID)
262 e = self.render_error(e)
262 e = self.render_error(e)
263 else:
263 else:
264 result = self._add_block_id_for_result(result, blockID=blockID)
264 result = self._add_block_id_for_result(result, blockID=blockID)
265 result = self.update_cell_prompt(result, blockID=blockID)
265 result = self.update_cell_prompt(result, blockID=blockID)
266 result = self.render_result(result)
266 result = self.render_result(result)
267
267
268 return result
268 return result
269
269
270
270
271 def _add_block_id_for_result(self, result, blockID):
271 def _add_block_id_for_result(self, result, blockID):
272 """Add the blockID to result or failure. Unfortunatley, we have to
272 """Add the blockID to result or failure. Unfortunatley, we have to
273 treat failures differently than result dicts.
273 treat failures differently than result dicts.
274 """
274 """
275
275
276 result['blockID'] = blockID
276 result['blockID'] = blockID
277
277
278 return result
278 return result
279
279
280 def _add_block_id_for_failure(self, failure, blockID):
280 def _add_block_id_for_failure(self, failure, blockID):
281 """_add_block_id_for_failure"""
281 """_add_block_id_for_failure"""
282
282
283 failure.blockID = blockID
283 failure.blockID = blockID
284 return failure
284 return failure
285
285
286
286
287 def _add_history(self, result, block=None):
287 def _add_history(self, result, block=None):
288 """Add block to the history"""
288 """Add block to the history"""
289
289
290 assert(block != None)
290 assert(block != None)
291 self.history.add_items([block])
291 self.history.add_items([block])
292 self.history_cursor += 1
292 self.history_cursor += 1
293
293
294 return result
294 return result
295
295
296
296
297 def get_history_previous(self, currentBlock):
297 def get_history_previous(self, current_block):
298 """ Returns previous history string and decrement history cursor.
298 """ Returns previous history string and decrement history cursor.
299 """
299 """
300 command = self.history.get_history_item(self.history_cursor - 1)
300 command = self.history.get_history_item(self.history_cursor - 1)
301
301
302 if command is not None:
302 if command is not None:
303 if(self.history_cursor == len(self.history.input_cache)):
303 if(self.history_cursor+1 == len(self.history.input_cache)):
304 self.history.input_cache[self.history_cursor] = currentBlock
304 self.history.input_cache[self.history_cursor] = current_block
305 self.history_cursor -= 1
305 self.history_cursor -= 1
306 return command
306 return command
307
307
308
308
309 def get_history_next(self):
309 def get_history_next(self):
310 """ Returns next history string and increment history cursor.
310 """ Returns next history string and increment history cursor.
311 """
311 """
312 command = self.history.get_history_item(self.history_cursor+1)
312 command = self.history.get_history_item(self.history_cursor+1)
313
313
314 if command is not None:
314 if command is not None:
315 self.history_cursor += 1
315 self.history_cursor += 1
316 return command
316 return command
317
317
318 ###
318 ###
319 # Subclasses probably want to override these methods...
319 # Subclasses probably want to override these methods...
320 ###
320 ###
321
321
322 def update_cell_prompt(self, result, blockID=None):
322 def update_cell_prompt(self, result, blockID=None):
323 """Subclass may override to update the input prompt for a block.
323 """Subclass may override to update the input prompt for a block.
324 Since this method will be called as a
324 Since this method will be called as a
325 twisted.internet.defer.Deferred's callback, implementations should
325 twisted.internet.defer.Deferred's callback, implementations should
326 return result when finished.
326 return result when finished.
327 """
327 """
328
328
329 return result
329 return result
330
330
331
331
332 def render_result(self, result):
332 def render_result(self, result):
333 """Subclasses must override to render result. Since this method will
333 """Subclasses must override to render result. Since this method will
334 be called as a twisted.internet.defer.Deferred's callback,
334 be called as a twisted.internet.defer.Deferred's callback,
335 implementations should return result when finished.
335 implementations should return result when finished.
336 """
336 """
337
337
338 return result
338 return result
339
339
340
340
341 def render_error(self, failure):
341 def render_error(self, failure):
342 """Subclasses must override to render the failure. Since this method
342 """Subclasses must override to render the failure. Since this method
343 will be called as a twisted.internet.defer.Deferred's callback,
343 will be called as a twisted.internet.defer.Deferred's callback,
344 implementations should return result when finished.
344 implementations should return result when finished.
345 """
345 """
346
346
347 return failure
347 return failure
348
348
349
349
350
350
351
351
@@ -1,123 +1,121 b''
1 """
1 """
2 Base front end class for all line-oriented frontends.
2 Base front end class for all line-oriented frontends.
3
3
4 Currently this focuses on synchronous frontends.
4 Currently this focuses on synchronous frontends.
5 """
5 """
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18 import re
18 import re
19
19
20 import IPython
20 import IPython
21
21
22
22
23 from frontendbase import FrontEndBase
23 from frontendbase import FrontEndBase
24 from IPython.kernel.core.interpreter import Interpreter
24 from IPython.kernel.core.interpreter import Interpreter
25
25
26 #-------------------------------------------------------------------------------
26 #-------------------------------------------------------------------------------
27 # Base class for the line-oriented front ends
27 # Base class for the line-oriented front ends
28 #-------------------------------------------------------------------------------
28 #-------------------------------------------------------------------------------
29 class LineFrontEndBase(FrontEndBase):
29 class LineFrontEndBase(FrontEndBase):
30
30
31 # Are we entering multi line input?
31 # Are we entering multi line input?
32 multi_line_input = False
32 multi_line_input = False
33
33
34 # The added tab stop to the string. It may, for instance, come from
35 # copy and pasting something with tabs.
36 tab_stop = 0
37 # FIXME: We still have to deal with this.
38
39 #--------------------------------------------------------------------------
34 #--------------------------------------------------------------------------
40 # Public API
35 # Public API
41 #--------------------------------------------------------------------------
36 #--------------------------------------------------------------------------
42
37
43 def __init__(self, shell=None, history=None):
38 def __init__(self, shell=None, history=None):
44 if shell is None:
39 if shell is None:
45 shell = Interpreter()
40 shell = Interpreter()
46 FrontEndBase.__init__(self, shell=shell, history=history)
41 FrontEndBase.__init__(self, shell=shell, history=history)
47
42
48 #FIXME: print banner.
43 #FIXME: print banner.
49 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
44 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
50 % IPython.__version__
45 % IPython.__version__
51
46
52
47
53 def complete(self, token):
48 def complete(self, token):
54 """Complete token in engine's user_ns
49 """Complete token in engine's user_ns
55
50
56 Parameters
51 Parameters
57 ----------
52 ----------
58 token : string
53 token : string
59
54
60 Result
55 Result
61 ------
56 ------
62 Deferred result of
57 Deferred result of
63 IPython.kernel.engineservice.IEngineBase.complete
58 IPython.kernel.engineservice.IEngineBase.complete
64 """
59 """
65
60
66 return self.shell.complete(token)
61 return self.shell.complete(token)
67
62
68
63
69 def render_result(self, result):
64 def render_result(self, result):
70 if 'stdout' in result and result['stdout']:
65 if 'stdout' in result and result['stdout']:
71 self.write('\n' + result['stdout'])
66 self.write('\n' + result['stdout'])
72 if 'display' in result and result['display']:
67 if 'display' in result and result['display']:
73 self.write("%s%s\n" % (
68 self.write("%s%s\n" % (
74 self.output_prompt % result['number'],
69 self.output_prompt % result['number'],
75 result['display']['pprint']
70 result['display']['pprint']
76 ) )
71 ) )
77
72
78
73
79 def render_error(self, failure):
74 def render_error(self, failure):
80 self.insert_text('\n\n'+str(failure)+'\n\n')
75 self.insert_text('\n\n'+str(failure)+'\n\n')
81 return failure
76 return failure
82
77
83
78
84 def on_enter(self):
79 def _on_enter(self):
85 """ Called when the return key is pressed in a line editing
80 """ Called when the return key is pressed in a line editing
86 buffer.
81 buffer.
87 """
82 """
88 current_buffer = self.get_current_edit_buffer()
83 current_buffer = self.get_current_edit_buffer()
89 current_buffer = current_buffer.replace('\r\n', '\n')
84 current_buffer = current_buffer.replace('\r\n', '\n')
90 current_buffer = current_buffer.replace('\t', 4*' ')
85 current_buffer = current_buffer.replace('\t', 4*' ')
91 cleaned_buffer = '\n'.join(l.rstrip()
86 cleaned_buffer = '\n'.join(l.rstrip()
92 for l in current_buffer.split('\n'))
87 for l in current_buffer.split('\n'))
93 if ( not self.multi_line_input
88 if ( not self.multi_line_input
94 or re.findall(r"\n[\t ]*$", cleaned_buffer)):
89 or re.findall(r"\n[\t ]*$", cleaned_buffer)):
95 if self.is_complete(cleaned_buffer):
90 if self.is_complete(cleaned_buffer):
96 self._add_history(None, cleaned_buffer.rstrip())
91 self.history.input_cache[-1] = \
92 current_buffer
97 result = self.shell.execute(cleaned_buffer)
93 result = self.shell.execute(cleaned_buffer)
98 self.render_result(result)
94 self.render_result(result)
99 self.new_prompt(self.prompt % (result['number'] + 1))
95 self.new_prompt(self.prompt % (result['number'] + 1))
100 self.multi_line_input = False
96 self.multi_line_input = False
97 # Start a new empty history entry
98 self._add_history(None, '')
101 else:
99 else:
102 if self.multi_line_input:
100 if self.multi_line_input:
103 self.write('\n' + self._get_indent_string(current_buffer))
101 self.write('\n' + self._get_indent_string(current_buffer))
104 else:
102 else:
105 self.multi_line_input = True
103 self.multi_line_input = True
106 self.write('\n\t')
104 self.write('\n\t')
107 else:
105 else:
108 self.write('\n'+self._get_indent_string(current_buffer))
106 self.write('\n'+self._get_indent_string(current_buffer))
109
107
110
108
111 #--------------------------------------------------------------------------
109 #--------------------------------------------------------------------------
112 # Private API
110 # Private API
113 #--------------------------------------------------------------------------
111 #--------------------------------------------------------------------------
114
112
115 def _get_indent_string(self, string):
113 def _get_indent_string(self, string):
116 string = string.split('\n')[-1]
114 string = string.split('\n')[-1]
117 indent_chars = len(string) - len(string.lstrip())
115 indent_chars = len(string) - len(string.lstrip())
118 indent_string = '\t'*(indent_chars // 4) + \
116 indent_string = '\t'*(indent_chars // 4) + \
119 ' '*(indent_chars % 4)
117 ' '*(indent_chars % 4)
120
118
121 return indent_string
119 return indent_string
122
120
123
121
@@ -1,388 +1,398 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
26
27 import re
27 import re
28
28
29 # FIXME: Need to provide an API for non user-generated display on the
29 # FIXME: Need to provide an API for non user-generated display on the
30 # screen: this should not be editable by the user.
30 # screen: this should not be editable by the user.
31
31
32 if wx.Platform == '__WXMSW__':
32 if wx.Platform == '__WXMSW__':
33 _DEFAULT_SIZE = 80
33 _DEFAULT_SIZE = 80
34 else:
34 else:
35 _DEFAULT_SIZE = 10
35 _DEFAULT_SIZE = 10
36
36
37 _DEFAULT_STYLE = {
37 _DEFAULT_STYLE = {
38 'stdout' : 'fore:#0000FF',
38 'stdout' : 'fore:#0000FF',
39 'stderr' : 'fore:#007f00',
39 'stderr' : 'fore:#007f00',
40 'trace' : 'fore:#FF0000',
40 'trace' : 'fore:#FF0000',
41
41
42 'default' : 'size:%d' % _DEFAULT_SIZE,
42 'default' : 'size:%d' % _DEFAULT_SIZE,
43 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
43 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
44 'bracebad' : 'fore:#000000,back:#FF0000,bold',
44 'bracebad' : 'fore:#000000,back:#FF0000,bold',
45
45
46 # properties for the various Python lexer styles
46 # properties for the various Python lexer styles
47 'comment' : 'fore:#007F00',
47 'comment' : 'fore:#007F00',
48 'number' : 'fore:#007F7F',
48 'number' : 'fore:#007F7F',
49 'string' : 'fore:#7F007F,italic',
49 'string' : 'fore:#7F007F,italic',
50 'char' : 'fore:#7F007F,italic',
50 'char' : 'fore:#7F007F,italic',
51 'keyword' : 'fore:#00007F,bold',
51 'keyword' : 'fore:#00007F,bold',
52 'triple' : 'fore:#7F0000',
52 'triple' : 'fore:#7F0000',
53 'tripledouble': 'fore:#7F0000',
53 'tripledouble': 'fore:#7F0000',
54 'class' : 'fore:#0000FF,bold,underline',
54 'class' : 'fore:#0000FF,bold,underline',
55 'def' : 'fore:#007F7F,bold',
55 'def' : 'fore:#007F7F,bold',
56 'operator' : 'bold',
56 'operator' : 'bold',
57
57
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 #-------------------------------------------------------------------------------
66 #-------------------------------------------------------------------------------
67 # The console widget class
67 # The console widget class
68 #-------------------------------------------------------------------------------
68 #-------------------------------------------------------------------------------
69 class ConsoleWidget(editwindow.EditWindow):
69 class ConsoleWidget(editwindow.EditWindow):
70 """ Specialized styled text control view for console-like workflow.
70 """ Specialized styled text control view for console-like workflow.
71
71
72 This widget is mainly interested in dealing with the prompt and
72 This widget is mainly interested in dealing with the prompt and
73 keeping the cursor inside the editing line.
73 keeping the cursor inside the editing line.
74 """
74 """
75
75
76 style = _DEFAULT_STYLE.copy()
76 style = _DEFAULT_STYLE.copy()
77
77
78 # Translation table from ANSI escape sequences to color. Override
78 # Translation table from ANSI escape sequences to color. Override
79 # this to specify your colors.
79 # this to specify your colors.
80 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
80 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
81 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
81 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
82 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
82 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
83 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
83 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
84 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
84 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
85 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
85 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
86 '1;34': [12, 'LIGHT BLUE'], '1;35':
86 '1;34': [12, 'LIGHT BLUE'], '1;35':
87 [13, 'MEDIUM VIOLET RED'],
87 [13, 'MEDIUM VIOLET RED'],
88 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
88 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
89
89
90 # The color of the carret (call _apply_style() after setting)
90 # The color of the carret (call _apply_style() after setting)
91 carret_color = 'BLACK'
91 carret_color = 'BLACK'
92
92
93
93
94 #--------------------------------------------------------------------------
94 #--------------------------------------------------------------------------
95 # Public API
95 # Public API
96 #--------------------------------------------------------------------------
96 #--------------------------------------------------------------------------
97
97
98 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
98 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
99 size=wx.DefaultSize, style=0,
99 size=wx.DefaultSize, style=0,
100 autocomplete_mode='IPYTHON'):
100 autocomplete_mode='IPYTHON'):
101 """ Autocomplete_mode: Can be 'IPYTHON' or 'STC'
101 """ Autocomplete_mode: Can be 'IPYTHON' or 'STC'
102 'IPYTHON' show autocompletion the ipython way
102 'IPYTHON' show autocompletion the ipython way
103 'STC" show it scintilla text control way
103 'STC" show it scintilla text control way
104 """
104 """
105 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
105 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
106 self.configure_scintilla()
106 self.configure_scintilla()
107
107
108 # FIXME: we need to retrieve this from the interpreter.
108 # FIXME: we need to retrieve this from the interpreter.
109 self.prompt = \
109 self.prompt = \
110 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
110 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
111 self.new_prompt(self.prompt % 1)
111 self.new_prompt(self.prompt % 1)
112
112
113 self.autocomplete_mode = autocomplete_mode
113 self.autocomplete_mode = autocomplete_mode
114
114
115 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
115 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
116 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
116
117
117
118
118 def configure_scintilla(self):
119 def configure_scintilla(self):
119 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
120 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
120 # the widget
121 # the widget
121 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
122 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
122 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
123 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
123 # Also allow Ctrl Shift "=" for poor non US keyboard users.
124 # Also allow Ctrl Shift "=" for poor non US keyboard users.
124 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
125 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
125 stc.STC_CMD_ZOOMIN)
126 stc.STC_CMD_ZOOMIN)
126
127
127 self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
128 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
128 stc.STC_CMD_PAGEUP)
129 # stc.STC_CMD_PAGEUP)
129
130
130 self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
131 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
131 stc.STC_CMD_PAGEDOWN)
132 # stc.STC_CMD_PAGEDOWN)
132
133
133 # Keys: we need to clear some of the keys the that don't play
134 # Keys: we need to clear some of the keys the that don't play
134 # well with a console.
135 # well with a console.
135 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
136 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
136 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
137 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
137 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
138 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
138
139
139
140
140 self.SetEOLMode(stc.STC_EOL_CRLF)
141 self.SetEOLMode(stc.STC_EOL_CRLF)
141 self.SetWrapMode(stc.STC_WRAP_CHAR)
142 self.SetWrapMode(stc.STC_WRAP_CHAR)
142 self.SetWrapMode(stc.STC_WRAP_WORD)
143 self.SetWrapMode(stc.STC_WRAP_WORD)
143 self.SetBufferedDraw(True)
144 self.SetBufferedDraw(True)
144 self.SetUseAntiAliasing(True)
145 self.SetUseAntiAliasing(True)
145 self.SetLayoutCache(stc.STC_CACHE_PAGE)
146 self.SetLayoutCache(stc.STC_CACHE_PAGE)
146 self.SetUndoCollection(False)
147 self.SetUndoCollection(False)
147 self.SetUseTabs(True)
148 self.SetUseTabs(True)
148 self.SetIndent(4)
149 self.SetIndent(4)
149 self.SetTabWidth(4)
150 self.SetTabWidth(4)
150
151
151 self.EnsureCaretVisible()
152 self.EnsureCaretVisible()
152
153
153 self.SetMargins(3, 3) #text is moved away from border with 3px
154 self.SetMargins(3, 3) #text is moved away from border with 3px
154 # Suppressing Scintilla margins
155 # Suppressing Scintilla margins
155 self.SetMarginWidth(0, 0)
156 self.SetMarginWidth(0, 0)
156 self.SetMarginWidth(1, 0)
157 self.SetMarginWidth(1, 0)
157 self.SetMarginWidth(2, 0)
158 self.SetMarginWidth(2, 0)
158
159
159 self._apply_style()
160 self._apply_style()
160
161
161 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
162 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
162
163
163 #self.SetEdgeMode(stc.STC_EDGE_LINE)
164 #self.SetEdgeMode(stc.STC_EDGE_LINE)
164 #self.SetEdgeColumn(80)
165 #self.SetEdgeColumn(80)
165
166
166 # styles
167 # styles
167 p = self.style
168 p = self.style
168 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
169 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
169 self.StyleClearAll()
170 self.StyleClearAll()
170 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
171 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
171 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
172 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
172 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
173 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
173
174
174 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
175 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
175 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
176 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
176 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
177 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
177 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
178 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
178 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
179 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
179 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
180 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
180 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
181 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
181 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
182 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
182 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
183 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
183 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
184 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
184 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
185 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
185 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
186 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
186 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
187 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
187
188
188
189
189 def write(self, text):
190 def write(self, text):
190 """ Write given text to buffer, while translating the ansi escape
191 """ Write given text to buffer, while translating the ansi escape
191 sequences.
192 sequences.
192 """
193 """
193 segments = self.color_pat.split(text)
194 segments = self.color_pat.split(text)
194 segment = segments.pop(0)
195 segment = segments.pop(0)
195 self.StartStyling(self.GetLength(), 0xFF)
196 self.StartStyling(self.GetLength(), 0xFF)
196 self.AppendText(segment)
197 self.AppendText(segment)
197
198
198 if segments:
199 if segments:
199 ansi_tags = self.color_pat.findall(text)
200 ansi_tags = self.color_pat.findall(text)
200
201
201 for tag in ansi_tags:
202 for tag in ansi_tags:
202 i = segments.index(tag)
203 i = segments.index(tag)
203 self.StartStyling(self.GetLength(), 0xFF)
204 self.StartStyling(self.GetLength(), 0xFF)
204 self.AppendText(segments[i+1])
205 self.AppendText(segments[i+1])
205
206
206 if tag != '0':
207 if tag != '0':
207 self.SetStyling(len(segments[i+1]),
208 self.SetStyling(len(segments[i+1]),
208 self.ANSI_STYLES[tag][0])
209 self.ANSI_STYLES[tag][0])
209
210
210 segments.pop(i)
211 segments.pop(i)
211
212
212 self.GotoPos(self.GetLength())
213 self.GotoPos(self.GetLength())
213
214
214
215
215 def new_prompt(self, prompt):
216 def new_prompt(self, prompt):
216 """ Prints a prompt at start of line, and move the start of the
217 """ Prints a prompt at start of line, and move the start of the
217 current block there.
218 current block there.
218
219
219 The prompt can be give with ascii escape sequences.
220 The prompt can be give with ascii escape sequences.
220 """
221 """
221 self.write(prompt)
222 self.write(prompt)
222 # now we update our cursor giving end of prompt
223 # now we update our cursor giving end of prompt
223 self.current_prompt_pos = self.GetLength()
224 self.current_prompt_pos = self.GetLength()
224 self.current_prompt_line = self.GetCurrentLine()
225 self.current_prompt_line = self.GetCurrentLine()
225
226
226
227
227 def replace_current_edit_buffer(self, text):
228 def replace_current_edit_buffer(self, text):
228 """ Replace currently entered command line with given text.
229 """ Replace currently entered command line with given text.
229 """
230 """
230 self.SetSelection(self.current_prompt_pos, self.GetLength())
231 self.SetSelection(self.current_prompt_pos, self.GetLength())
231 self.ReplaceSelection(text)
232 self.ReplaceSelection(text)
232 self.GotoPos(self.GetLength())
233 self.GotoPos(self.GetLength())
233
234
234
235
235 def get_current_edit_buffer(self):
236 def get_current_edit_buffer(self):
236 """ Returns the text in current edit buffer.
237 """ Returns the text in current edit buffer.
237 """
238 """
238 return self.GetTextRange(self.current_prompt_pos,
239 return self.GetTextRange(self.current_prompt_pos,
239 self.GetLength())
240 self.GetLength())
240
241
241
242
242 #--------------------------------------------------------------------------
243 #--------------------------------------------------------------------------
243 # Private API
244 # Private API
244 #--------------------------------------------------------------------------
245 #--------------------------------------------------------------------------
245
246
246 def _apply_style(self):
247 def _apply_style(self):
247 """ Applies the colors for the different text elements and the
248 """ Applies the colors for the different text elements and the
248 carret.
249 carret.
249 """
250 """
250 self.SetCaretForeground(self.carret_color)
251 self.SetCaretForeground(self.carret_color)
251
252
252 self.StyleClearAll()
253 self.StyleClearAll()
253 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
254 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
254 "fore:#FF0000,back:#0000FF,bold")
255 "fore:#FF0000,back:#0000FF,bold")
255 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
256 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
256 "fore:#000000,back:#FF0000,bold")
257 "fore:#000000,back:#FF0000,bold")
257
258
258 for style in self.ANSI_STYLES.values():
259 for style in self.ANSI_STYLES.values():
259 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
260 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
260
261
261
262
262 def removeFromTo(self, from_pos, to_pos):
263 if from_pos < to_pos:
264 self.SetSelection(from_pos, to_pos)
265 self.DeleteBack()
266
267
268 def selectFromTo(self, from_pos, to_pos):
269 self.SetSelectionStart(from_pos)
270 self.SetSelectionEnd(to_pos)
271
272
273 def writeCompletion(self, possibilities):
263 def writeCompletion(self, possibilities):
274 if self.autocomplete_mode == 'IPYTHON':
264 if self.autocomplete_mode == 'IPYTHON':
275 max_len = len(max(possibilities, key=len))
265 max_len = len(max(possibilities, key=len))
276 max_symbol = ' '*max_len
266 max_symbol = ' '*max_len
277
267
278 #now we check how much symbol we can put on a line...
268 #now we check how much symbol we can put on a line...
279 test_buffer = max_symbol + ' '*4
269 test_buffer = max_symbol + ' '*4
280
270
281 allowed_symbols = 80/len(test_buffer)
271 allowed_symbols = 80/len(test_buffer)
282 if allowed_symbols == 0:
272 if allowed_symbols == 0:
283 allowed_symbols = 1
273 allowed_symbols = 1
284
274
285 pos = 1
275 pos = 1
286 buf = ''
276 buf = ''
287 for symbol in possibilities:
277 for symbol in possibilities:
288 #buf += symbol+'\n'#*spaces)
278 #buf += symbol+'\n'#*spaces)
289 if pos < allowed_symbols:
279 if pos < allowed_symbols:
290 spaces = max_len - len(symbol) + 4
280 spaces = max_len - len(symbol) + 4
291 buf += symbol+' '*spaces
281 buf += symbol+' '*spaces
292 pos += 1
282 pos += 1
293 else:
283 else:
294 buf += symbol+'\n'
284 buf += symbol+'\n'
295 pos = 1
285 pos = 1
296 self.write(buf)
286 self.write(buf)
297 else:
287 else:
298 possibilities.sort() # Python sorts are case sensitive
288 possibilities.sort() # Python sorts are case sensitive
299 self.AutoCompSetIgnoreCase(False)
289 self.AutoCompSetIgnoreCase(False)
300 self.AutoCompSetAutoHide(False)
290 self.AutoCompSetAutoHide(False)
301 #let compute the length ot last word
291 #let compute the length ot last word
302 splitter = [' ', '(', '[', '{']
292 splitter = [' ', '(', '[', '{']
303 last_word = self.get_current_edit_buffer()
293 last_word = self.get_current_edit_buffer()
304 for breaker in splitter:
294 for breaker in splitter:
305 last_word = last_word.split(breaker)[-1]
295 last_word = last_word.split(breaker)[-1]
306 self.AutoCompShow(len(last_word), " ".join(possibilities))
296 self.AutoCompShow(len(last_word), " ".join(possibilities))
307
297
308
298
309 def scroll_to_bottom(self):
299 def scroll_to_bottom(self):
310 maxrange = self.GetScrollRange(wx.VERTICAL)
300 maxrange = self.GetScrollRange(wx.VERTICAL)
311 self.ScrollLines(maxrange)
301 self.ScrollLines(maxrange)
312
302
303 def on_enter(self):
304 """ Called when the return key is hit.
305 """
306 pass
307
313
308
314 def _on_key_down(self, event, skip=True):
309 def _on_key_down(self, event, skip=True):
315 """ Key press callback used for correcting behavior for
310 """ Key press callback used for correcting behavior for
316 console-like interfaces: the cursor is constraint to be after
311 console-like interfaces: the cursor is constraint to be after
317 the last prompt.
312 the last prompt.
318
313
319 Return True if event as been catched.
314 Return True if event as been catched.
320 """
315 """
321 catched = False
316 catched = False
317 # Intercept some specific keys.
322 if event.KeyCode == ord('L') and event.ControlDown() :
318 if event.KeyCode == ord('L') and event.ControlDown() :
323 skip = False
324 catched = True
319 catched = True
325 self.scroll_to_bottom()
320 self.scroll_to_bottom()
321 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
322 catched = True
323 self.ScrollPages(-1)
324 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
325 catched = True
326 self.ScrollPages(1)
326
327
327 if self.AutoCompActive():
328 if self.AutoCompActive():
328 event.Skip()
329 event.Skip()
329 else:
330 else:
330 if event.KeyCode == wx.WXK_HOME:
331 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
332 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
333 catched = True
334 self._on_enter()
335
336 elif event.KeyCode == wx.WXK_HOME:
331 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
337 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
332 self.GotoPos(self.current_prompt_pos)
338 self.GotoPos(self.current_prompt_pos)
333 catched = True
339 catched = True
334
340
335 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
341 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
336 self.selectFromTo(self.current_prompt_pos,
342 # FIXME: This behavior is not ideal: if the selection
337 self.GetCurrentPos())
343 # is already started, it will jump.
344 self.SetSelectionStart(self.current_prompt_pos)
345 self.SetSelectionEnd(self.GetCurrentPos())
338 catched = True
346 catched = True
339
347
340 elif event.KeyCode == wx.WXK_UP:
348 elif event.KeyCode == wx.WXK_UP:
341 if self.GetCurrentLine() > self.current_prompt_line:
349 if self.GetCurrentLine() > self.current_prompt_line:
342 if self.GetCurrentLine() == self.current_prompt_line + 1 \
350 if self.GetCurrentLine() == self.current_prompt_line + 1 \
343 and self.GetColumn(self.GetCurrentPos()) < \
351 and self.GetColumn(self.GetCurrentPos()) < \
344 self.GetColumn(self.current_prompt_pos):
352 self.GetColumn(self.current_prompt_pos):
345 self.GotoPos(self.current_prompt_pos)
353 self.GotoPos(self.current_prompt_pos)
346 else:
354 else:
347 event.Skip()
355 event.Skip()
348 catched = True
356 catched = True
349
357
350 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
358 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
351 if self.GetCurrentPos() > self.current_prompt_pos:
359 if self.GetCurrentPos() > self.current_prompt_pos:
352 event.Skip()
360 event.Skip()
353 catched = True
361 catched = True
354
362
355 if skip and not catched:
363 if skip and not catched:
356 event.Skip()
364 event.Skip()
357
365
358 if event.KeyCode not in (wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)\
366 return catched
359 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
367
360 wx.MOD_SHIFT):
368
361 # If cursor is outside the editing region, put it back.
369 def _on_key_up(self, event, skip=True):
370 """ If cursor is outside the editing region, put it back.
371 """
372 event.Skip()
362 if self.GetCurrentPos() < self.current_prompt_pos:
373 if self.GetCurrentPos() < self.current_prompt_pos:
363 self.GotoPos(self.current_prompt_pos)
374 self.GotoPos(self.current_prompt_pos)
364
375
365 return catched
366
376
367
377
368
378
369 if __name__ == '__main__':
379 if __name__ == '__main__':
370 # Some simple code to test the console widget.
380 # Some simple code to test the console widget.
371 class MainWindow(wx.Frame):
381 class MainWindow(wx.Frame):
372 def __init__(self, parent, id, title):
382 def __init__(self, parent, id, title):
373 wx.Frame.__init__(self, parent, id, title, size=(300,250))
383 wx.Frame.__init__(self, parent, id, title, size=(300,250))
374 self._sizer = wx.BoxSizer(wx.VERTICAL)
384 self._sizer = wx.BoxSizer(wx.VERTICAL)
375 self.console_widget = ConsoleWidget(self)
385 self.console_widget = ConsoleWidget(self)
376 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
386 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
377 self.SetSizer(self._sizer)
387 self.SetSizer(self._sizer)
378 self.SetAutoLayout(1)
388 self.SetAutoLayout(1)
379 self.Show(True)
389 self.Show(True)
380
390
381 app = wx.PySimpleApp()
391 app = wx.PySimpleApp()
382 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
392 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
383 w.SetSize((780, 460))
393 w.SetSize((780, 460))
384 w.Show()
394 w.Show()
385
395
386 app.MainLoop()
396 app.MainLoop()
387
397
388
398
@@ -1,111 +1,110 b''
1 # encoding: utf-8 -*- test-case-name:
1 # encoding: utf-8 -*- test-case-name:
2 # FIXME: Need to add tests.
2 # FIXME: Need to add tests.
3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
4
4
5 """Classes to provide a Wx frontend to the
5 """Classes to provide a Wx frontend to the
6 ipython1.kernel.engineservice.EngineService.
6 ipython1.kernel.engineservice.EngineService.
7
7
8 """
8 """
9
9
10 __docformat__ = "restructuredtext en"
10 __docformat__ = "restructuredtext en"
11
11
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
13 # Copyright (C) 2008 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22
22
23
23
24 import wx
24 import wx
25 from console_widget import ConsoleWidget
25 from console_widget import ConsoleWidget
26
26
27 from IPython.frontend.linefrontendbase import LineFrontEndBase
27 from IPython.frontend.linefrontendbase import LineFrontEndBase
28
28
29 #-------------------------------------------------------------------------------
29 #-------------------------------------------------------------------------------
30 # Classes to implement the Wx frontend
30 # Classes to implement the Wx frontend
31 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
32
32
33
33
34
34
35
35
36 class IPythonWxController(LineFrontEndBase, ConsoleWidget):
36 class IPythonWxController(LineFrontEndBase, ConsoleWidget):
37
37
38 output_prompt = \
38 output_prompt = \
39 '\n\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
39 '\n\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
40
40
41 #--------------------------------------------------------------------------
41 #--------------------------------------------------------------------------
42 # Public API
42 # Public API
43 #--------------------------------------------------------------------------
43 #--------------------------------------------------------------------------
44
44
45 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
45 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
46 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
46 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
47 *args, **kwds):
47 *args, **kwds):
48 """ Create Shell instance.
48 """ Create Shell instance.
49 """
49 """
50 ConsoleWidget.__init__(self, parent, id, pos, size, style)
50 ConsoleWidget.__init__(self, parent, id, pos, size, style)
51 LineFrontEndBase.__init__(self)
51 LineFrontEndBase.__init__(self)
52
52
53 # Capture Character keys
53 # Capture Character keys
54 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
54 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
55
55
56 #--------------------------------------------------------------------------
56 #--------------------------------------------------------------------------
57 # Private API
57 # Private API
58 #--------------------------------------------------------------------------
58 #--------------------------------------------------------------------------
59
59
60
60
61 def _on_key_down(self, event, skip=True):
61 def _on_key_down(self, event, skip=True):
62 """ Capture the character events, let the parent
62 """ Capture the character events, let the parent
63 widget handle them, and put our logic afterward.
63 widget handle them, and put our logic afterward.
64 """
64 """
65 current_line_number = self.GetCurrentLine()
65 current_line_number = self.GetCurrentLine()
66 # Capture enter
67 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
68 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
69 self.on_enter()
70 # Up history
66 # Up history
71 elif event.KeyCode == wx.WXK_UP and (
67 if event.KeyCode == wx.WXK_UP and (
72 ( current_line_number == self.current_prompt_line and
68 ( current_line_number == self.current_prompt_line and
73 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
69 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
74 or event.ControlDown() ):
70 or event.ControlDown() ):
75 new_buffer = self.get_history_previous(
71 new_buffer = self.get_history_previous(
76 self.get_current_edit_buffer())
72 self.get_current_edit_buffer())
77 if new_buffer is not None:
73 if new_buffer is not None:
78 self.replace_current_edit_buffer(new_buffer)
74 self.replace_current_edit_buffer(new_buffer)
75 if self.GetCurrentLine() > self.current_prompt_line:
76 # Go to first line, for seemless history up.
77 self.GotoPos(self.current_prompt_pos)
79 # Down history
78 # Down history
80 elif event.KeyCode == wx.WXK_DOWN and (
79 elif event.KeyCode == wx.WXK_DOWN and (
81 ( current_line_number == self.LineCount -1 and
80 ( current_line_number == self.LineCount -1 and
82 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
81 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
83 or event.ControlDown() ):
82 or event.ControlDown() ):
84 new_buffer = self.get_history_next()
83 new_buffer = self.get_history_next()
85 if new_buffer is not None:
84 if new_buffer is not None:
86 self.replace_current_edit_buffer(new_buffer)
85 self.replace_current_edit_buffer(new_buffer)
87 else:
86 else:
88 ConsoleWidget._on_key_down(self, event, skip=True)
87 ConsoleWidget._on_key_down(self, event, skip=skip)
89
88
90
89
91
90
92
91
93 if __name__ == '__main__':
92 if __name__ == '__main__':
94 class MainWindow(wx.Frame):
93 class MainWindow(wx.Frame):
95 def __init__(self, parent, id, title):
94 def __init__(self, parent, id, title):
96 wx.Frame.__init__(self, parent, id, title, size=(300,250))
95 wx.Frame.__init__(self, parent, id, title, size=(300,250))
97 self._sizer = wx.BoxSizer(wx.VERTICAL)
96 self._sizer = wx.BoxSizer(wx.VERTICAL)
98 self.shell = IPythonWxController(self)
97 self.shell = IPythonWxController(self)
99 self._sizer.Add(self.shell, 1, wx.EXPAND)
98 self._sizer.Add(self.shell, 1, wx.EXPAND)
100 self.SetSizer(self._sizer)
99 self.SetSizer(self._sizer)
101 self.SetAutoLayout(1)
100 self.SetAutoLayout(1)
102 self.Show(True)
101 self.Show(True)
103
102
104 app = wx.PySimpleApp()
103 app = wx.PySimpleApp()
105 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
104 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
106 frame.shell.SetFocus()
105 frame.shell.SetFocus()
107 frame.SetSize((660, 460))
106 frame.SetSize((660, 460))
108 self = frame.shell
107 self = frame.shell
109
108
110 app.MainLoop()
109 app.MainLoop()
111
110
@@ -1,137 +1,137 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """ Manage the input and output history of the interpreter and the
3 """ Manage the input and output history of the interpreter and the
4 frontend.
4 frontend.
5
5
6 There are 2 different history objects, one that lives in the interpreter,
6 There are 2 different history objects, one that lives in the interpreter,
7 and one that lives in the frontend. They are synced with a diff at each
7 and one that lives in the frontend. They are synced with a diff at each
8 execution of a command, as the interpreter history is a real stack, its
8 execution of a command, as the interpreter history is a real stack, its
9 existing entries are not mutable.
9 existing entries are not mutable.
10 """
10 """
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
24
25 from copy import copy
25 from copy import copy
26
26
27 # Local imports.
27 # Local imports.
28 from util import InputList
28 from util import InputList
29
29
30
30
31 ##############################################################################
31 ##############################################################################
32 class History(object):
32 class History(object):
33 """ An object managing the input and output history.
33 """ An object managing the input and output history.
34 """
34 """
35
35
36 def __init__(self, input_cache=None, output_cache=None):
36 def __init__(self, input_cache=None, output_cache=None):
37
37
38 # Stuff that IPython adds to the namespace.
38 # Stuff that IPython adds to the namespace.
39 self.namespace_additions = dict(
39 self.namespace_additions = dict(
40 _ = None,
40 _ = None,
41 __ = None,
41 __ = None,
42 ___ = None,
42 ___ = None,
43 )
43 )
44
44
45 # A list to store input commands.
45 # A list to store input commands.
46 if input_cache is None:
46 if input_cache is None:
47 input_cache =InputList([])
47 input_cache =InputList([])
48 self.input_cache = input_cache
48 self.input_cache = input_cache
49
49
50 # A dictionary to store trapped output.
50 # A dictionary to store trapped output.
51 if output_cache is None:
51 if output_cache is None:
52 output_cache = {}
52 output_cache = {}
53 self.output_cache = output_cache
53 self.output_cache = output_cache
54
54
55 def get_history_item(self, index):
55 def get_history_item(self, index):
56 """ Returns the history string at index, where index is the
56 """ Returns the history string at index, where index is the
57 distance from the end (positive).
57 distance from the end (positive).
58 """
58 """
59 if index>0 and index<len(self.input_cache):
59 if index>=0 and index<len(self.input_cache):
60 return self.input_cache[index]
60 return self.input_cache[index]
61
61
62
62
63 ##############################################################################
63 ##############################################################################
64 class InterpreterHistory(History):
64 class InterpreterHistory(History):
65 """ An object managing the input and output history at the interpreter
65 """ An object managing the input and output history at the interpreter
66 level.
66 level.
67 """
67 """
68
68
69 def setup_namespace(self, namespace):
69 def setup_namespace(self, namespace):
70 """ Add the input and output caches into the interpreter's namespace
70 """ Add the input and output caches into the interpreter's namespace
71 with IPython-conventional names.
71 with IPython-conventional names.
72
72
73 Parameters
73 Parameters
74 ----------
74 ----------
75 namespace : dict
75 namespace : dict
76 """
76 """
77
77
78 namespace['In'] = self.input_cache
78 namespace['In'] = self.input_cache
79 namespace['_ih'] = self.input_cache
79 namespace['_ih'] = self.input_cache
80 namespace['Out'] = self.output_cache
80 namespace['Out'] = self.output_cache
81 namespace['_oh'] = self.output_cache
81 namespace['_oh'] = self.output_cache
82
82
83 def update_history(self, interpreter, python):
83 def update_history(self, interpreter, python):
84 """ Update the history objects that this object maintains and the
84 """ Update the history objects that this object maintains and the
85 interpreter's namespace.
85 interpreter's namespace.
86
86
87 Parameters
87 Parameters
88 ----------
88 ----------
89 interpreter : Interpreter
89 interpreter : Interpreter
90 python : str
90 python : str
91 The real Python code that was translated and actually executed.
91 The real Python code that was translated and actually executed.
92 """
92 """
93
93
94 number = interpreter.current_cell_number
94 number = interpreter.current_cell_number
95
95
96 new_obj = interpreter.display_trap.obj
96 new_obj = interpreter.display_trap.obj
97 if new_obj is not None:
97 if new_obj is not None:
98 self.namespace_additions['___'] = self.namespace_additions['__']
98 self.namespace_additions['___'] = self.namespace_additions['__']
99 self.namespace_additions['__'] = self.namespace_additions['_']
99 self.namespace_additions['__'] = self.namespace_additions['_']
100 self.namespace_additions['_'] = new_obj
100 self.namespace_additions['_'] = new_obj
101 self.output_cache[number] = new_obj
101 self.output_cache[number] = new_obj
102
102
103 interpreter.user_ns.update(self.namespace_additions)
103 interpreter.user_ns.update(self.namespace_additions)
104 self.input_cache.add(number, python)
104 self.input_cache.add(number, python)
105
105
106
106
107 def get_history_item(self, index):
107 def get_history_item(self, index):
108 """ Returns the history string at index, where index is the
108 """ Returns the history string at index, where index is the
109 distance from the end (positive).
109 distance from the end (positive).
110 """
110 """
111 if index>0 and index<(len(self.input_cache)-1):
111 if index>0 and index<(len(self.input_cache)-1):
112 return self.input_cache[-index]
112 return self.input_cache[-index]
113
113
114 def get_input_cache(self):
114 def get_input_cache(self):
115 return copy(self.input_cache)
115 return copy(self.input_cache)
116
116
117 def get_input_after(self, index):
117 def get_input_after(self, index):
118 """ Returns the list of the commands entered after index.
118 """ Returns the list of the commands entered after index.
119 """
119 """
120 # We need to call directly list.__getslice__, because this object
120 # We need to call directly list.__getslice__, because this object
121 # is not a real list.
121 # is not a real list.
122 return list.__getslice__(self.input_cache, index,
122 return list.__getslice__(self.input_cache, index,
123 len(self.input_cache))
123 len(self.input_cache))
124
124
125
125
126 ##############################################################################
126 ##############################################################################
127 class FrontEndHistory(History):
127 class FrontEndHistory(History):
128 """ An object managing the input and output history at the frontend.
128 """ An object managing the input and output history at the frontend.
129 It is used as a local cache to reduce network latency problems
129 It is used as a local cache to reduce network latency problems
130 and multiple users editing the same thing.
130 and multiple users editing the same thing.
131 """
131 """
132
132
133 def add_items(self, item_list):
133 def add_items(self, item_list):
134 """ Adds the given command list to the stack of executed
134 """ Adds the given command list to the stack of executed
135 commands.
135 commands.
136 """
136 """
137 self.input_cache.extend(item_list)
137 self.input_cache.extend(item_list)
General Comments 0
You need to be logged in to leave comments. Login now