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