##// END OF EJS Templates
Expanded %exit magic in qtconsole to not exit without prompting and...
Erik Tollerud -
Show More
@@ -1,463 +1,465 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3
3
4 TODO: Add support for retrieving the system default editor. Requires code
4 TODO: Add support for retrieving the system default editor. Requires code
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
6 Linux (use the xdg system).
6 Linux (use the xdg system).
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 # Standard library imports
13 # Standard library imports
14 from collections import namedtuple
14 from collections import namedtuple
15 import re
15 import re
16 from subprocess import Popen
16 from subprocess import Popen
17 from textwrap import dedent
17 from textwrap import dedent
18
18
19 # System library imports
19 # System library imports
20 from PyQt4 import QtCore, QtGui
20 from PyQt4 import QtCore, QtGui
21
21
22 # Local imports
22 # Local imports
23 from IPython.core.inputsplitter import IPythonInputSplitter, \
23 from IPython.core.inputsplitter import IPythonInputSplitter, \
24 transform_ipy_prompt
24 transform_ipy_prompt
25 from IPython.core.usage import default_gui_banner
25 from IPython.core.usage import default_gui_banner
26 from IPython.utils.traitlets import Bool, Str
26 from IPython.utils.traitlets import Bool, Str
27 from frontend_widget import FrontendWidget
27 from frontend_widget import FrontendWidget
28 from styles import (default_light_style_sheet, default_light_syntax_style,
28 from styles import (default_light_style_sheet, default_light_syntax_style,
29 default_dark_style_sheet, default_dark_syntax_style,
29 default_dark_style_sheet, default_dark_syntax_style,
30 default_bw_style_sheet, default_bw_syntax_style)
30 default_bw_style_sheet, default_bw_syntax_style)
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Constants
33 # Constants
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 # Default strings to build and display input and output prompts (and separators
36 # Default strings to build and display input and output prompts (and separators
37 # in between)
37 # in between)
38 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
38 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
39 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
39 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
40 default_input_sep = '\n'
40 default_input_sep = '\n'
41 default_output_sep = ''
41 default_output_sep = ''
42 default_output_sep2 = ''
42 default_output_sep2 = ''
43
43
44 # Base path for most payload sources.
44 # Base path for most payload sources.
45 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
45 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # IPythonWidget class
48 # IPythonWidget class
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51 class IPythonWidget(FrontendWidget):
51 class IPythonWidget(FrontendWidget):
52 """ A FrontendWidget for an IPython kernel.
52 """ A FrontendWidget for an IPython kernel.
53 """
53 """
54
54
55 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
55 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
56 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
56 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
57 # settings.
57 # settings.
58 custom_edit = Bool(False)
58 custom_edit = Bool(False)
59 custom_edit_requested = QtCore.pyqtSignal(object, object)
59 custom_edit_requested = QtCore.pyqtSignal(object, object)
60
60
61 # A command for invoking a system text editor. If the string contains a
61 # A command for invoking a system text editor. If the string contains a
62 # {filename} format specifier, it will be used. Otherwise, the filename will
62 # {filename} format specifier, it will be used. Otherwise, the filename will
63 # be appended to the end the command.
63 # be appended to the end the command.
64 editor = Str('default', config=True)
64 editor = Str('default', config=True)
65
65
66 # The editor command to use when a specific line number is requested. The
66 # The editor command to use when a specific line number is requested. The
67 # string should contain two format specifiers: {line} and {filename}. If
67 # string should contain two format specifiers: {line} and {filename}. If
68 # this parameter is not specified, the line number option to the %edit magic
68 # this parameter is not specified, the line number option to the %edit magic
69 # will be ignored.
69 # will be ignored.
70 editor_line = Str(config=True)
70 editor_line = Str(config=True)
71
71
72 # A CSS stylesheet. The stylesheet can contain classes for:
72 # A CSS stylesheet. The stylesheet can contain classes for:
73 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
73 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
74 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
74 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
75 # 3. IPython: .error, .in-prompt, .out-prompt, etc
75 # 3. IPython: .error, .in-prompt, .out-prompt, etc
76 style_sheet = Str(config=True)
76 style_sheet = Str(config=True)
77
77
78 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
78 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
79 # the style sheet is queried for Pygments style information.
79 # the style sheet is queried for Pygments style information.
80 syntax_style = Str(config=True)
80 syntax_style = Str(config=True)
81
81
82 # Prompts.
82 # Prompts.
83 in_prompt = Str(default_in_prompt, config=True)
83 in_prompt = Str(default_in_prompt, config=True)
84 out_prompt = Str(default_out_prompt, config=True)
84 out_prompt = Str(default_out_prompt, config=True)
85 input_sep = Str(default_input_sep, config=True)
85 input_sep = Str(default_input_sep, config=True)
86 output_sep = Str(default_output_sep, config=True)
86 output_sep = Str(default_output_sep, config=True)
87 output_sep2 = Str(default_output_sep2, config=True)
87 output_sep2 = Str(default_output_sep2, config=True)
88
88
89 # FrontendWidget protected class variables.
89 # FrontendWidget protected class variables.
90 _input_splitter_class = IPythonInputSplitter
90 _input_splitter_class = IPythonInputSplitter
91
91
92 # IPythonWidget protected class variables.
92 # IPythonWidget protected class variables.
93 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
93 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
94 _payload_source_edit = zmq_shell_source + '.edit_magic'
94 _payload_source_edit = zmq_shell_source + '.edit_magic'
95 _payload_source_exit = zmq_shell_source + '.ask_exit'
95 _payload_source_exit = zmq_shell_source + '.ask_exit'
96 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
96 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
97 _payload_source_page = 'IPython.zmq.page.page'
97 _payload_source_page = 'IPython.zmq.page.page'
98
98
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100 # 'object' interface
100 # 'object' interface
101 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
102
102
103 def __init__(self, *args, **kw):
103 def __init__(self, *args, **kw):
104 super(IPythonWidget, self).__init__(*args, **kw)
104 super(IPythonWidget, self).__init__(*args, **kw)
105
105
106 # IPythonWidget protected variables.
106 # IPythonWidget protected variables.
107 self._code_to_load = None
107 self._code_to_load = None
108 self._payload_handlers = {
108 self._payload_handlers = {
109 self._payload_source_edit : self._handle_payload_edit,
109 self._payload_source_edit : self._handle_payload_edit,
110 self._payload_source_exit : self._handle_payload_exit,
110 self._payload_source_exit : self._handle_payload_exit,
111 self._payload_source_page : self._handle_payload_page,
111 self._payload_source_page : self._handle_payload_page,
112 self._payload_source_loadpy : self._handle_payload_loadpy }
112 self._payload_source_loadpy : self._handle_payload_loadpy }
113 self._previous_prompt_obj = None
113 self._previous_prompt_obj = None
114 self._keep_kernel_on_exit = None
114
115
115 # Initialize widget styling.
116 # Initialize widget styling.
116 if self.style_sheet:
117 if self.style_sheet:
117 self._style_sheet_changed()
118 self._style_sheet_changed()
118 self._syntax_style_changed()
119 self._syntax_style_changed()
119 else:
120 else:
120 self.set_default_style()
121 self.set_default_style()
121
122
122 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
123 # 'BaseFrontendMixin' abstract interface
124 # 'BaseFrontendMixin' abstract interface
124 #---------------------------------------------------------------------------
125 #---------------------------------------------------------------------------
125
126
126 def _handle_complete_reply(self, rep):
127 def _handle_complete_reply(self, rep):
127 """ Reimplemented to support IPython's improved completion machinery.
128 """ Reimplemented to support IPython's improved completion machinery.
128 """
129 """
129 cursor = self._get_cursor()
130 cursor = self._get_cursor()
130 info = self._request_info.get('complete')
131 info = self._request_info.get('complete')
131 if info and info.id == rep['parent_header']['msg_id'] and \
132 if info and info.id == rep['parent_header']['msg_id'] and \
132 info.pos == cursor.position():
133 info.pos == cursor.position():
133 matches = rep['content']['matches']
134 matches = rep['content']['matches']
134 text = rep['content']['matched_text']
135 text = rep['content']['matched_text']
135 offset = len(text)
136 offset = len(text)
136
137
137 # Clean up matches with period and path separators if the matched
138 # Clean up matches with period and path separators if the matched
138 # text has not been transformed. This is done by truncating all
139 # text has not been transformed. This is done by truncating all
139 # but the last component and then suitably decreasing the offset
140 # but the last component and then suitably decreasing the offset
140 # between the current cursor position and the start of completion.
141 # between the current cursor position and the start of completion.
141 if len(matches) > 1 and matches[0][:offset] == text:
142 if len(matches) > 1 and matches[0][:offset] == text:
142 parts = re.split(r'[./\\]', text)
143 parts = re.split(r'[./\\]', text)
143 sep_count = len(parts) - 1
144 sep_count = len(parts) - 1
144 if sep_count:
145 if sep_count:
145 chop_length = sum(map(len, parts[:sep_count])) + sep_count
146 chop_length = sum(map(len, parts[:sep_count])) + sep_count
146 matches = [ match[chop_length:] for match in matches ]
147 matches = [ match[chop_length:] for match in matches ]
147 offset -= chop_length
148 offset -= chop_length
148
149
149 # Move the cursor to the start of the match and complete.
150 # Move the cursor to the start of the match and complete.
150 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
151 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
151 self._complete_with_items(cursor, matches)
152 self._complete_with_items(cursor, matches)
152
153
153 def _handle_execute_reply(self, msg):
154 def _handle_execute_reply(self, msg):
154 """ Reimplemented to support prompt requests.
155 """ Reimplemented to support prompt requests.
155 """
156 """
156 info = self._request_info.get('execute')
157 info = self._request_info.get('execute')
157 if info and info.id == msg['parent_header']['msg_id']:
158 if info and info.id == msg['parent_header']['msg_id']:
158 if info.kind == 'prompt':
159 if info.kind == 'prompt':
159 number = msg['content']['execution_count'] + 1
160 number = msg['content']['execution_count'] + 1
160 self._show_interpreter_prompt(number)
161 self._show_interpreter_prompt(number)
161 else:
162 else:
162 super(IPythonWidget, self)._handle_execute_reply(msg)
163 super(IPythonWidget, self)._handle_execute_reply(msg)
163
164
164 def _handle_history_reply(self, msg):
165 def _handle_history_reply(self, msg):
165 """ Implemented to handle history replies, which are only supported by
166 """ Implemented to handle history replies, which are only supported by
166 the IPython kernel.
167 the IPython kernel.
167 """
168 """
168 history_dict = msg['content']['history']
169 history_dict = msg['content']['history']
169 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
170 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
170 self._set_history(items)
171 self._set_history(items)
171
172
172 def _handle_pyout(self, msg):
173 def _handle_pyout(self, msg):
173 """ Reimplemented for IPython-style "display hook".
174 """ Reimplemented for IPython-style "display hook".
174 """
175 """
175 if not self._hidden and self._is_from_this_session(msg):
176 if not self._hidden and self._is_from_this_session(msg):
176 content = msg['content']
177 content = msg['content']
177 prompt_number = content['execution_count']
178 prompt_number = content['execution_count']
178 self._append_plain_text(self.output_sep)
179 self._append_plain_text(self.output_sep)
179 self._append_html(self._make_out_prompt(prompt_number))
180 self._append_html(self._make_out_prompt(prompt_number))
180 self._append_plain_text(content['data']+self.output_sep2)
181 self._append_plain_text(content['data']+self.output_sep2)
181
182
182 def _started_channels(self):
183 def _started_channels(self):
183 """ Reimplemented to make a history request.
184 """ Reimplemented to make a history request.
184 """
185 """
185 super(IPythonWidget, self)._started_channels()
186 super(IPythonWidget, self)._started_channels()
186 # FIXME: Disabled until history requests are properly implemented.
187 # FIXME: Disabled until history requests are properly implemented.
187 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
188 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
188
189
189 #---------------------------------------------------------------------------
190 #---------------------------------------------------------------------------
190 # 'ConsoleWidget' public interface
191 # 'ConsoleWidget' public interface
191 #---------------------------------------------------------------------------
192 #---------------------------------------------------------------------------
192
193
193 def copy(self):
194 def copy(self):
194 """ Copy the currently selected text to the clipboard, removing prompts
195 """ Copy the currently selected text to the clipboard, removing prompts
195 if possible.
196 if possible.
196 """
197 """
197 text = unicode(self._control.textCursor().selection().toPlainText())
198 text = unicode(self._control.textCursor().selection().toPlainText())
198 if text:
199 if text:
199 lines = map(transform_ipy_prompt, text.splitlines())
200 lines = map(transform_ipy_prompt, text.splitlines())
200 text = '\n'.join(lines)
201 text = '\n'.join(lines)
201 QtGui.QApplication.clipboard().setText(text)
202 QtGui.QApplication.clipboard().setText(text)
202
203
203 #---------------------------------------------------------------------------
204 #---------------------------------------------------------------------------
204 # 'FrontendWidget' public interface
205 # 'FrontendWidget' public interface
205 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
206
207
207 def execute_file(self, path, hidden=False):
208 def execute_file(self, path, hidden=False):
208 """ Reimplemented to use the 'run' magic.
209 """ Reimplemented to use the 'run' magic.
209 """
210 """
210 self.execute('%%run %s' % path, hidden=hidden)
211 self.execute('%%run %s' % path, hidden=hidden)
211
212
212 #---------------------------------------------------------------------------
213 #---------------------------------------------------------------------------
213 # 'FrontendWidget' protected interface
214 # 'FrontendWidget' protected interface
214 #---------------------------------------------------------------------------
215 #---------------------------------------------------------------------------
215
216
216 def _complete(self):
217 def _complete(self):
217 """ Reimplemented to support IPython's improved completion machinery.
218 """ Reimplemented to support IPython's improved completion machinery.
218 """
219 """
219 # We let the kernel split the input line, so we *always* send an empty
220 # We let the kernel split the input line, so we *always* send an empty
220 # text field. Readline-based frontends do get a real text field which
221 # text field. Readline-based frontends do get a real text field which
221 # they can use.
222 # they can use.
222 text = ''
223 text = ''
223
224
224 # Send the completion request to the kernel
225 # Send the completion request to the kernel
225 msg_id = self.kernel_manager.xreq_channel.complete(
226 msg_id = self.kernel_manager.xreq_channel.complete(
226 text, # text
227 text, # text
227 self._get_input_buffer_cursor_line(), # line
228 self._get_input_buffer_cursor_line(), # line
228 self._get_input_buffer_cursor_column(), # cursor_pos
229 self._get_input_buffer_cursor_column(), # cursor_pos
229 self.input_buffer) # block
230 self.input_buffer) # block
230 pos = self._get_cursor().position()
231 pos = self._get_cursor().position()
231 info = self._CompletionRequest(msg_id, pos)
232 info = self._CompletionRequest(msg_id, pos)
232 self._request_info['complete'] = info
233 self._request_info['complete'] = info
233
234
234 def _get_banner(self):
235 def _get_banner(self):
235 """ Reimplemented to return IPython's default banner.
236 """ Reimplemented to return IPython's default banner.
236 """
237 """
237 return default_gui_banner
238 return default_gui_banner
238
239
239 def _process_execute_error(self, msg):
240 def _process_execute_error(self, msg):
240 """ Reimplemented for IPython-style traceback formatting.
241 """ Reimplemented for IPython-style traceback formatting.
241 """
242 """
242 content = msg['content']
243 content = msg['content']
243 traceback = '\n'.join(content['traceback']) + '\n'
244 traceback = '\n'.join(content['traceback']) + '\n'
244 if False:
245 if False:
245 # FIXME: For now, tracebacks come as plain text, so we can't use
246 # FIXME: For now, tracebacks come as plain text, so we can't use
246 # the html renderer yet. Once we refactor ultratb to produce
247 # the html renderer yet. Once we refactor ultratb to produce
247 # properly styled tracebacks, this branch should be the default
248 # properly styled tracebacks, this branch should be the default
248 traceback = traceback.replace(' ', '&nbsp;')
249 traceback = traceback.replace(' ', '&nbsp;')
249 traceback = traceback.replace('\n', '<br/>')
250 traceback = traceback.replace('\n', '<br/>')
250
251
251 ename = content['ename']
252 ename = content['ename']
252 ename_styled = '<span class="error">%s</span>' % ename
253 ename_styled = '<span class="error">%s</span>' % ename
253 traceback = traceback.replace(ename, ename_styled)
254 traceback = traceback.replace(ename, ename_styled)
254
255
255 self._append_html(traceback)
256 self._append_html(traceback)
256 else:
257 else:
257 # This is the fallback for now, using plain text with ansi escapes
258 # This is the fallback for now, using plain text with ansi escapes
258 self._append_plain_text(traceback)
259 self._append_plain_text(traceback)
259
260
260 def _process_execute_payload(self, item):
261 def _process_execute_payload(self, item):
261 """ Reimplemented to dispatch payloads to handler methods.
262 """ Reimplemented to dispatch payloads to handler methods.
262 """
263 """
263 handler = self._payload_handlers.get(item['source'])
264 handler = self._payload_handlers.get(item['source'])
264 if handler is None:
265 if handler is None:
265 # We have no handler for this type of payload, simply ignore it
266 # We have no handler for this type of payload, simply ignore it
266 return False
267 return False
267 else:
268 else:
268 handler(item)
269 handler(item)
269 return True
270 return True
270
271
271 def _show_interpreter_prompt(self, number=None):
272 def _show_interpreter_prompt(self, number=None):
272 """ Reimplemented for IPython-style prompts.
273 """ Reimplemented for IPython-style prompts.
273 """
274 """
274 # If a number was not specified, make a prompt number request.
275 # If a number was not specified, make a prompt number request.
275 if number is None:
276 if number is None:
276 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
277 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
277 info = self._ExecutionRequest(msg_id, 'prompt')
278 info = self._ExecutionRequest(msg_id, 'prompt')
278 self._request_info['execute'] = info
279 self._request_info['execute'] = info
279 return
280 return
280
281
281 # Show a new prompt and save information about it so that it can be
282 # Show a new prompt and save information about it so that it can be
282 # updated later if the prompt number turns out to be wrong.
283 # updated later if the prompt number turns out to be wrong.
283 self._prompt_sep = self.input_sep
284 self._prompt_sep = self.input_sep
284 self._show_prompt(self._make_in_prompt(number), html=True)
285 self._show_prompt(self._make_in_prompt(number), html=True)
285 block = self._control.document().lastBlock()
286 block = self._control.document().lastBlock()
286 length = len(self._prompt)
287 length = len(self._prompt)
287 self._previous_prompt_obj = self._PromptBlock(block, length, number)
288 self._previous_prompt_obj = self._PromptBlock(block, length, number)
288
289
289 # Update continuation prompt to reflect (possibly) new prompt length.
290 # Update continuation prompt to reflect (possibly) new prompt length.
290 self._set_continuation_prompt(
291 self._set_continuation_prompt(
291 self._make_continuation_prompt(self._prompt), html=True)
292 self._make_continuation_prompt(self._prompt), html=True)
292
293
293 # Load code from the %loadpy magic, if necessary.
294 # Load code from the %loadpy magic, if necessary.
294 if self._code_to_load is not None:
295 if self._code_to_load is not None:
295 self.input_buffer = dedent(unicode(self._code_to_load).rstrip())
296 self.input_buffer = dedent(unicode(self._code_to_load).rstrip())
296 self._code_to_load = None
297 self._code_to_load = None
297
298
298 def _show_interpreter_prompt_for_reply(self, msg):
299 def _show_interpreter_prompt_for_reply(self, msg):
299 """ Reimplemented for IPython-style prompts.
300 """ Reimplemented for IPython-style prompts.
300 """
301 """
301 # Update the old prompt number if necessary.
302 # Update the old prompt number if necessary.
302 content = msg['content']
303 content = msg['content']
303 previous_prompt_number = content['execution_count']
304 previous_prompt_number = content['execution_count']
304 if self._previous_prompt_obj and \
305 if self._previous_prompt_obj and \
305 self._previous_prompt_obj.number != previous_prompt_number:
306 self._previous_prompt_obj.number != previous_prompt_number:
306 block = self._previous_prompt_obj.block
307 block = self._previous_prompt_obj.block
307
308
308 # Make sure the prompt block has not been erased.
309 # Make sure the prompt block has not been erased.
309 if block.isValid() and not block.text().isEmpty():
310 if block.isValid() and not block.text().isEmpty():
310
311
311 # Remove the old prompt and insert a new prompt.
312 # Remove the old prompt and insert a new prompt.
312 cursor = QtGui.QTextCursor(block)
313 cursor = QtGui.QTextCursor(block)
313 cursor.movePosition(QtGui.QTextCursor.Right,
314 cursor.movePosition(QtGui.QTextCursor.Right,
314 QtGui.QTextCursor.KeepAnchor,
315 QtGui.QTextCursor.KeepAnchor,
315 self._previous_prompt_obj.length)
316 self._previous_prompt_obj.length)
316 prompt = self._make_in_prompt(previous_prompt_number)
317 prompt = self._make_in_prompt(previous_prompt_number)
317 self._prompt = self._insert_html_fetching_plain_text(
318 self._prompt = self._insert_html_fetching_plain_text(
318 cursor, prompt)
319 cursor, prompt)
319
320
320 # When the HTML is inserted, Qt blows away the syntax
321 # When the HTML is inserted, Qt blows away the syntax
321 # highlighting for the line, so we need to rehighlight it.
322 # highlighting for the line, so we need to rehighlight it.
322 self._highlighter.rehighlightBlock(cursor.block())
323 self._highlighter.rehighlightBlock(cursor.block())
323
324
324 self._previous_prompt_obj = None
325 self._previous_prompt_obj = None
325
326
326 # Show a new prompt with the kernel's estimated prompt number.
327 # Show a new prompt with the kernel's estimated prompt number.
327 self._show_interpreter_prompt(previous_prompt_number + 1)
328 self._show_interpreter_prompt(previous_prompt_number + 1)
328
329
329 #---------------------------------------------------------------------------
330 #---------------------------------------------------------------------------
330 # 'IPythonWidget' interface
331 # 'IPythonWidget' interface
331 #---------------------------------------------------------------------------
332 #---------------------------------------------------------------------------
332
333
333 def set_default_style(self, colors='lightbg'):
334 def set_default_style(self, colors='lightbg'):
334 """ Sets the widget style to the class defaults.
335 """ Sets the widget style to the class defaults.
335
336
336 Parameters:
337 Parameters:
337 -----------
338 -----------
338 colors : str, optional (default lightbg)
339 colors : str, optional (default lightbg)
339 Whether to use the default IPython light background or dark
340 Whether to use the default IPython light background or dark
340 background or B&W style.
341 background or B&W style.
341 """
342 """
342 colors = colors.lower()
343 colors = colors.lower()
343 if colors=='lightbg':
344 if colors=='lightbg':
344 self.style_sheet = default_light_style_sheet
345 self.style_sheet = default_light_style_sheet
345 self.syntax_style = default_light_syntax_style
346 self.syntax_style = default_light_syntax_style
346 elif colors=='linux':
347 elif colors=='linux':
347 self.style_sheet = default_dark_style_sheet
348 self.style_sheet = default_dark_style_sheet
348 self.syntax_style = default_dark_syntax_style
349 self.syntax_style = default_dark_syntax_style
349 elif colors=='nocolor':
350 elif colors=='nocolor':
350 self.style_sheet = default_bw_style_sheet
351 self.style_sheet = default_bw_style_sheet
351 self.syntax_style = default_bw_syntax_style
352 self.syntax_style = default_bw_syntax_style
352 else:
353 else:
353 raise KeyError("No such color scheme: %s"%colors)
354 raise KeyError("No such color scheme: %s"%colors)
354
355
355 #---------------------------------------------------------------------------
356 #---------------------------------------------------------------------------
356 # 'IPythonWidget' protected interface
357 # 'IPythonWidget' protected interface
357 #---------------------------------------------------------------------------
358 #---------------------------------------------------------------------------
358
359
359 def _edit(self, filename, line=None):
360 def _edit(self, filename, line=None):
360 """ Opens a Python script for editing.
361 """ Opens a Python script for editing.
361
362
362 Parameters:
363 Parameters:
363 -----------
364 -----------
364 filename : str
365 filename : str
365 A path to a local system file.
366 A path to a local system file.
366
367
367 line : int, optional
368 line : int, optional
368 A line of interest in the file.
369 A line of interest in the file.
369 """
370 """
370 if self.custom_edit:
371 if self.custom_edit:
371 self.custom_edit_requested.emit(filename, line)
372 self.custom_edit_requested.emit(filename, line)
372 elif self.editor == 'default':
373 elif self.editor == 'default':
373 self._append_plain_text('No default editor available.\n')
374 self._append_plain_text('No default editor available.\n')
374 else:
375 else:
375 try:
376 try:
376 filename = '"%s"' % filename
377 filename = '"%s"' % filename
377 if line and self.editor_line:
378 if line and self.editor_line:
378 command = self.editor_line.format(filename=filename,
379 command = self.editor_line.format(filename=filename,
379 line=line)
380 line=line)
380 else:
381 else:
381 try:
382 try:
382 command = self.editor.format()
383 command = self.editor.format()
383 except KeyError:
384 except KeyError:
384 command = self.editor.format(filename=filename)
385 command = self.editor.format(filename=filename)
385 else:
386 else:
386 command += ' ' + filename
387 command += ' ' + filename
387 except KeyError:
388 except KeyError:
388 self._append_plain_text('Invalid editor command.\n')
389 self._append_plain_text('Invalid editor command.\n')
389 else:
390 else:
390 try:
391 try:
391 Popen(command, shell=True)
392 Popen(command, shell=True)
392 except OSError:
393 except OSError:
393 msg = 'Opening editor with command "%s" failed.\n'
394 msg = 'Opening editor with command "%s" failed.\n'
394 self._append_plain_text(msg % command)
395 self._append_plain_text(msg % command)
395
396
396 def _make_in_prompt(self, number):
397 def _make_in_prompt(self, number):
397 """ Given a prompt number, returns an HTML In prompt.
398 """ Given a prompt number, returns an HTML In prompt.
398 """
399 """
399 body = self.in_prompt % number
400 body = self.in_prompt % number
400 return '<span class="in-prompt">%s</span>' % body
401 return '<span class="in-prompt">%s</span>' % body
401
402
402 def _make_continuation_prompt(self, prompt):
403 def _make_continuation_prompt(self, prompt):
403 """ Given a plain text version of an In prompt, returns an HTML
404 """ Given a plain text version of an In prompt, returns an HTML
404 continuation prompt.
405 continuation prompt.
405 """
406 """
406 end_chars = '...: '
407 end_chars = '...: '
407 space_count = len(prompt.lstrip('\n')) - len(end_chars)
408 space_count = len(prompt.lstrip('\n')) - len(end_chars)
408 body = '&nbsp;' * space_count + end_chars
409 body = '&nbsp;' * space_count + end_chars
409 return '<span class="in-prompt">%s</span>' % body
410 return '<span class="in-prompt">%s</span>' % body
410
411
411 def _make_out_prompt(self, number):
412 def _make_out_prompt(self, number):
412 """ Given a prompt number, returns an HTML Out prompt.
413 """ Given a prompt number, returns an HTML Out prompt.
413 """
414 """
414 body = self.out_prompt % number
415 body = self.out_prompt % number
415 return '<span class="out-prompt">%s</span>' % body
416 return '<span class="out-prompt">%s</span>' % body
416
417
417 #------ Payload handlers --------------------------------------------------
418 #------ Payload handlers --------------------------------------------------
418
419
419 # Payload handlers with a generic interface: each takes the opaque payload
420 # Payload handlers with a generic interface: each takes the opaque payload
420 # dict, unpacks it and calls the underlying functions with the necessary
421 # dict, unpacks it and calls the underlying functions with the necessary
421 # arguments.
422 # arguments.
422
423
423 def _handle_payload_edit(self, item):
424 def _handle_payload_edit(self, item):
424 self._edit(item['filename'], item['line_number'])
425 self._edit(item['filename'], item['line_number'])
425
426
426 def _handle_payload_exit(self, item):
427 def _handle_payload_exit(self, item):
428 self._keep_kernel_on_exit = item['keepkernel']
427 self.exit_requested.emit()
429 self.exit_requested.emit()
428
430
429 def _handle_payload_loadpy(self, item):
431 def _handle_payload_loadpy(self, item):
430 # Simple save the text of the .py file for later. The text is written
432 # Simple save the text of the .py file for later. The text is written
431 # to the buffer when _prompt_started_hook is called.
433 # to the buffer when _prompt_started_hook is called.
432 self._code_to_load = item['text']
434 self._code_to_load = item['text']
433
435
434 def _handle_payload_page(self, item):
436 def _handle_payload_page(self, item):
435 # Since the plain text widget supports only a very small subset of HTML
437 # Since the plain text widget supports only a very small subset of HTML
436 # and we have no control over the HTML source, we only page HTML
438 # and we have no control over the HTML source, we only page HTML
437 # payloads in the rich text widget.
439 # payloads in the rich text widget.
438 if item['html'] and self.kind == 'rich':
440 if item['html'] and self.kind == 'rich':
439 self._page(item['html'], html=True)
441 self._page(item['html'], html=True)
440 else:
442 else:
441 self._page(item['text'], html=False)
443 self._page(item['text'], html=False)
442
444
443 #------ Trait change handlers ---------------------------------------------
445 #------ Trait change handlers ---------------------------------------------
444
446
445 def _style_sheet_changed(self):
447 def _style_sheet_changed(self):
446 """ Set the style sheets of the underlying widgets.
448 """ Set the style sheets of the underlying widgets.
447 """
449 """
448 self.setStyleSheet(self.style_sheet)
450 self.setStyleSheet(self.style_sheet)
449 self._control.document().setDefaultStyleSheet(self.style_sheet)
451 self._control.document().setDefaultStyleSheet(self.style_sheet)
450 if self._page_control:
452 if self._page_control:
451 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
453 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
452
454
453 bg_color = self._control.palette().background().color()
455 bg_color = self._control.palette().background().color()
454 self._ansi_processor.set_background_color(bg_color)
456 self._ansi_processor.set_background_color(bg_color)
455
457
456 def _syntax_style_changed(self):
458 def _syntax_style_changed(self):
457 """ Set the style for the syntax highlighter.
459 """ Set the style for the syntax highlighter.
458 """
460 """
459 if self.syntax_style:
461 if self.syntax_style:
460 self._highlighter.set_style(self.syntax_style)
462 self._highlighter.set_style(self.syntax_style)
461 else:
463 else:
462 self._highlighter.set_style_sheet(self.style_sheet)
464 self._highlighter.set_style_sheet(self.style_sheet)
463
465
@@ -1,254 +1,266 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Imports
5 # Imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # Systemm library imports
8 # Systemm library imports
9 from PyQt4 import QtGui
9 from PyQt4 import QtGui
10 from pygments.styles import get_all_styles
10 from pygments.styles import get_all_styles
11 # Local imports
11 # Local imports
12 from IPython.external.argparse import ArgumentParser
12 from IPython.external.argparse import ArgumentParser
13 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
13 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
14 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
14 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
15 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
15 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
16 from IPython.frontend.qt.console import styles
16 from IPython.frontend.qt.console import styles
17 from IPython.frontend.qt.kernelmanager import QtKernelManager
17 from IPython.frontend.qt.kernelmanager import QtKernelManager
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Network Constants
20 # Network Constants
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
23 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Classes
26 # Classes
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 class MainWindow(QtGui.QMainWindow):
29 class MainWindow(QtGui.QMainWindow):
30
30
31 #---------------------------------------------------------------------------
31 #---------------------------------------------------------------------------
32 # 'object' interface
32 # 'object' interface
33 #---------------------------------------------------------------------------
33 #---------------------------------------------------------------------------
34
34
35 def __init__(self, app, frontend, existing=False, may_close=True):
35 def __init__(self, app, frontend, existing=False, may_close=True):
36 """ Create a MainWindow for the specified FrontendWidget.
36 """ Create a MainWindow for the specified FrontendWidget.
37
37
38 The app is passed as an argument to allow for different
38 The app is passed as an argument to allow for different
39 closing behavior depending on whether we are the Kernel's parent.
39 closing behavior depending on whether we are the Kernel's parent.
40
40
41 If existing is True, then this Console does not own the Kernel.
41 If existing is True, then this Console does not own the Kernel.
42
42
43 If may_close is True, then this Console is permitted to close the kernel
43 If may_close is True, then this Console is permitted to close the kernel
44 """
44 """
45 super(MainWindow, self).__init__()
45 super(MainWindow, self).__init__()
46 self._app = app
46 self._app = app
47 self._frontend = frontend
47 self._frontend = frontend
48 self._existing = existing
48 self._existing = existing
49 if existing:
49 if existing:
50 self._may_close = may_close
50 self._may_close = may_close
51 else:
51 else:
52 self._may_close = True
52 self._may_close = True
53 self._frontend.exit_requested.connect(self.close)
53 self._frontend.exit_requested.connect(self.close)
54 self.setCentralWidget(frontend)
54 self.setCentralWidget(frontend)
55
55
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57 # QWidget interface
57 # QWidget interface
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59
59
60 def closeEvent(self, event):
60 def closeEvent(self, event):
61 """ Reimplemented to prompt the user and close the kernel cleanly.
61 """ Reimplemented to prompt the user and close the kernel cleanly.
62 """
62 """
63 keepkernel = self._frontend._keep_kernel_on_exit
63 kernel_manager = self._frontend.kernel_manager
64 kernel_manager = self._frontend.kernel_manager
64 if kernel_manager and kernel_manager.channels_running:
65
65 title = self.window().windowTitle()
66 if keepkernel is None:
66 cancel = QtGui.QMessageBox.Cancel
67 if kernel_manager and kernel_manager.channels_running:
67 okay = QtGui.QMessageBox.Ok
68 title = self.window().windowTitle()
68 if self._may_close:
69 cancel = QtGui.QMessageBox.Cancel
69 msg = "You are closing this Console window."
70 okay = QtGui.QMessageBox.Ok
70 info = "Would you like to quit the Kernel and all attached Consoles as well?"
71 if self._may_close:
71 justthis = QtGui.QPushButton("&No, just this Console", self)
72 msg = "You are closing this Console window."
72 justthis.setShortcut('N')
73 info = "Would you like to quit the Kernel and all attached Consoles as well?"
73 closeall = QtGui.QPushButton("&Yes, quit everything", self)
74 justthis = QtGui.QPushButton("&No, just this Console", self)
74 closeall.setShortcut('Y')
75 justthis.setShortcut('N')
75 box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg)
76 closeall = QtGui.QPushButton("&Yes, quit everything", self)
76 box.setInformativeText(info)
77 closeall.setShortcut('Y')
77 box.addButton(cancel)
78 box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg)
78 box.addButton(justthis, QtGui.QMessageBox.NoRole)
79 box.setInformativeText(info)
79 box.addButton(closeall, QtGui.QMessageBox.YesRole)
80 box.addButton(cancel)
80 box.setDefaultButton(closeall)
81 box.addButton(justthis, QtGui.QMessageBox.NoRole)
81 box.setEscapeButton(cancel)
82 box.addButton(closeall, QtGui.QMessageBox.YesRole)
82 reply = box.exec_()
83 box.setDefaultButton(closeall)
83 if reply == 1: # close All
84 box.setEscapeButton(cancel)
84 kernel_manager.shutdown_kernel()
85 reply = box.exec_()
85 #kernel_manager.stop_channels()
86 if reply == 1: # close All
86 event.accept()
87 kernel_manager.shutdown_kernel()
87 elif reply == 0: # close Console
88 #kernel_manager.stop_channels()
88 if not self._existing:
89 event.accept()
89 # I have the kernel: don't quit, just close the window
90 elif reply == 0: # close Console
90 self._app.setQuitOnLastWindowClosed(False)
91 if not self._existing:
91 self.deleteLater()
92 # Have kernel: don't quit, just close the window
92 event.accept()
93 self._app.setQuitOnLastWindowClosed(False)
93 else:
94 self.deleteLater()
94 event.ignore()
95 event.accept()
95 else:
96 else:
96 reply = QtGui.QMessageBox.question(self, title,
97 event.ignore()
97 "Are you sure you want to close this Console?"+
98 "\nThe Kernel and other Consoles will remain active.",
99 okay|cancel,
100 defaultButton=okay
101 )
102 if reply == okay:
103 event.accept()
104 else:
98 else:
105 event.ignore()
99 reply = QtGui.QMessageBox.question(self, title,
106
100 "Are you sure you want to close this Console?"+
101 "\nThe Kernel and other Consoles will remain active.",
102 okay|cancel,
103 defaultButton=okay
104 )
105 if reply == okay:
106 event.accept()
107 else:
108 event.ignore()
109 elif keepkernel: #close console but leave kernel running
110 if kernel_manager and kernel_manager.channels_running:
111 if not self._existing:
112 # I have the kernel: don't quit, just close the window
113 self._app.setQuitOnLastWindowClosed(False)
114 event.accept()
115 else: #close console and kernel
116 if kernel_manager and kernel_manager.channels_running:
117 kernel_manager.shutdown_kernel()
118 event.accept()
107
119
108 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
109 # Main entry point
121 # Main entry point
110 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
111
123
112 def main():
124 def main():
113 """ Entry point for application.
125 """ Entry point for application.
114 """
126 """
115 # Parse command line arguments.
127 # Parse command line arguments.
116 parser = ArgumentParser()
128 parser = ArgumentParser()
117 kgroup = parser.add_argument_group('kernel options')
129 kgroup = parser.add_argument_group('kernel options')
118 kgroup.add_argument('-e', '--existing', action='store_true',
130 kgroup.add_argument('-e', '--existing', action='store_true',
119 help='connect to an existing kernel')
131 help='connect to an existing kernel')
120 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
132 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
121 help=\
133 help=\
122 "set the kernel\'s IP address [default localhost].\
134 "set the kernel\'s IP address [default localhost].\
123 If the IP address is something other than localhost, then \
135 If the IP address is something other than localhost, then \
124 Consoles on other machines will be able to connect\
136 Consoles on other machines will be able to connect\
125 to the Kernel, so be careful!")
137 to the Kernel, so be careful!")
126 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
138 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
127 help='set the XREQ channel port [default random]')
139 help='set the XREQ channel port [default random]')
128 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
140 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
129 help='set the SUB channel port [default random]')
141 help='set the SUB channel port [default random]')
130 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
142 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
131 help='set the REP channel port [default random]')
143 help='set the REP channel port [default random]')
132 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
144 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
133 help='set the heartbeat port [default random]')
145 help='set the heartbeat port [default random]')
134
146
135 egroup = kgroup.add_mutually_exclusive_group()
147 egroup = kgroup.add_mutually_exclusive_group()
136 egroup.add_argument('--pure', action='store_true', help = \
148 egroup.add_argument('--pure', action='store_true', help = \
137 'use a pure Python kernel instead of an IPython kernel')
149 'use a pure Python kernel instead of an IPython kernel')
138 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
150 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
139 const='auto', help = \
151 const='auto', help = \
140 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
152 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
141 given, the GUI backend is matplotlib's, otherwise use one of: \
153 given, the GUI backend is matplotlib's, otherwise use one of: \
142 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
154 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
143
155
144 wgroup = parser.add_argument_group('widget options')
156 wgroup = parser.add_argument_group('widget options')
145 wgroup.add_argument('--paging', type=str, default='inside',
157 wgroup.add_argument('--paging', type=str, default='inside',
146 choices = ['inside', 'hsplit', 'vsplit', 'none'],
158 choices = ['inside', 'hsplit', 'vsplit', 'none'],
147 help='set the paging style [default inside]')
159 help='set the paging style [default inside]')
148 wgroup.add_argument('--rich', action='store_true',
160 wgroup.add_argument('--rich', action='store_true',
149 help='enable rich text support')
161 help='enable rich text support')
150 wgroup.add_argument('--gui-completion', action='store_true',
162 wgroup.add_argument('--gui-completion', action='store_true',
151 help='use a GUI widget for tab completion')
163 help='use a GUI widget for tab completion')
152 wgroup.add_argument('--style', type=str,
164 wgroup.add_argument('--style', type=str,
153 choices = list(get_all_styles()),
165 choices = list(get_all_styles()),
154 help='specify a pygments style for by name.')
166 help='specify a pygments style for by name.')
155 wgroup.add_argument('--stylesheet', type=str,
167 wgroup.add_argument('--stylesheet', type=str,
156 help="path to a custom CSS stylesheet.")
168 help="path to a custom CSS stylesheet.")
157 wgroup.add_argument('--colors', type=str,
169 wgroup.add_argument('--colors', type=str,
158 help="Set the color scheme (LightBG,Linux,NoColor). This is guessed\
170 help="Set the color scheme (LightBG,Linux,NoColor). This is guessed\
159 based on the pygments style if not set.")
171 based on the pygments style if not set.")
160
172
161 args = parser.parse_args()
173 args = parser.parse_args()
162
174
163 # parse the colors arg down to current known labels
175 # parse the colors arg down to current known labels
164 if args.colors:
176 if args.colors:
165 colors=args.colors.lower()
177 colors=args.colors.lower()
166 if colors in ('lightbg', 'light'):
178 if colors in ('lightbg', 'light'):
167 colors='lightbg'
179 colors='lightbg'
168 elif colors in ('dark', 'linux'):
180 elif colors in ('dark', 'linux'):
169 colors='linux'
181 colors='linux'
170 else:
182 else:
171 colors='nocolor'
183 colors='nocolor'
172 elif args.style:
184 elif args.style:
173 if args.style=='bw':
185 if args.style=='bw':
174 colors='nocolor'
186 colors='nocolor'
175 elif styles.dark_style(args.style):
187 elif styles.dark_style(args.style):
176 colors='linux'
188 colors='linux'
177 else:
189 else:
178 colors='lightbg'
190 colors='lightbg'
179 else:
191 else:
180 colors=None
192 colors=None
181
193
182 # Don't let Qt or ZMQ swallow KeyboardInterupts.
194 # Don't let Qt or ZMQ swallow KeyboardInterupts.
183 import signal
195 import signal
184 signal.signal(signal.SIGINT, signal.SIG_DFL)
196 signal.signal(signal.SIGINT, signal.SIG_DFL)
185
197
186 # Create a KernelManager and start a kernel.
198 # Create a KernelManager and start a kernel.
187 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
199 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
188 sub_address=(args.ip, args.sub),
200 sub_address=(args.ip, args.sub),
189 rep_address=(args.ip, args.rep),
201 rep_address=(args.ip, args.rep),
190 hb_address=(args.ip, args.hb))
202 hb_address=(args.ip, args.hb))
191 if not args.existing:
203 if not args.existing:
192 # if not args.ip in LOCAL_IPS+ALL_ALIAS:
204 # if not args.ip in LOCAL_IPS+ALL_ALIAS:
193 # raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS)
205 # raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS)
194
206
195 kwargs = dict(ip=args.ip)
207 kwargs = dict(ip=args.ip)
196 if args.pure:
208 if args.pure:
197 kwargs['ipython']=False
209 kwargs['ipython']=False
198 else:
210 else:
199 kwargs['colors']=colors
211 kwargs['colors']=colors
200 if args.pylab:
212 if args.pylab:
201 kwargs['pylab']=args.pylab
213 kwargs['pylab']=args.pylab
202
214
203 kernel_manager.start_kernel(**kwargs)
215 kernel_manager.start_kernel(**kwargs)
204 kernel_manager.start_channels()
216 kernel_manager.start_channels()
205
217
206 local_kernel = (not args.existing) or args.ip in LOCAL_IPS
218 local_kernel = (not args.existing) or args.ip in LOCAL_IPS
207 # Create the widget.
219 # Create the widget.
208 app = QtGui.QApplication([])
220 app = QtGui.QApplication([])
209 if args.pure:
221 if args.pure:
210 kind = 'rich' if args.rich else 'plain'
222 kind = 'rich' if args.rich else 'plain'
211 widget = FrontendWidget(kind=kind, paging=args.paging, local_kernel=local_kernel)
223 widget = FrontendWidget(kind=kind, paging=args.paging, local_kernel=local_kernel)
212 elif args.rich or args.pylab:
224 elif args.rich or args.pylab:
213 widget = RichIPythonWidget(paging=args.paging, local_kernel=local_kernel)
225 widget = RichIPythonWidget(paging=args.paging, local_kernel=local_kernel)
214 else:
226 else:
215 widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
227 widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
216 widget.gui_completion = args.gui_completion
228 widget.gui_completion = args.gui_completion
217 widget.kernel_manager = kernel_manager
229 widget.kernel_manager = kernel_manager
218
230
219 # configure the style:
231 # configure the style:
220 if not args.pure: # only IPythonWidget supports styles
232 if not args.pure: # only IPythonWidget supports styles
221 if args.style:
233 if args.style:
222 widget.syntax_style = args.style
234 widget.syntax_style = args.style
223 widget.style_sheet = styles.sheet_from_template(args.style, colors)
235 widget.style_sheet = styles.sheet_from_template(args.style, colors)
224 widget._syntax_style_changed()
236 widget._syntax_style_changed()
225 widget._style_sheet_changed()
237 widget._style_sheet_changed()
226 elif colors:
238 elif colors:
227 # use a default style
239 # use a default style
228 widget.set_default_style(colors=colors)
240 widget.set_default_style(colors=colors)
229 else:
241 else:
230 # this is redundant for now, but allows the widget's
242 # this is redundant for now, but allows the widget's
231 # defaults to change
243 # defaults to change
232 widget.set_default_style()
244 widget.set_default_style()
233
245
234 if args.stylesheet:
246 if args.stylesheet:
235 # we got an expicit stylesheet
247 # we got an expicit stylesheet
236 if os.path.isfile(args.stylesheet):
248 if os.path.isfile(args.stylesheet):
237 with open(args.stylesheet) as f:
249 with open(args.stylesheet) as f:
238 sheet = f.read()
250 sheet = f.read()
239 widget.style_sheet = sheet
251 widget.style_sheet = sheet
240 widget._style_sheet_changed()
252 widget._style_sheet_changed()
241 else:
253 else:
242 raise IOError("Stylesheet %r not found."%args.stylesheet)
254 raise IOError("Stylesheet %r not found."%args.stylesheet)
243
255
244 # Create the main window.
256 # Create the main window.
245 window = MainWindow(app, widget, args.existing, may_close=local_kernel)
257 window = MainWindow(app, widget, args.existing, may_close=local_kernel)
246 window.setWindowTitle('Python' if args.pure else 'IPython')
258 window.setWindowTitle('Python' if args.pure else 'IPython')
247 window.show()
259 window.show()
248
260
249 # Start the application main loop.
261 # Start the application main loop.
250 app.exec_()
262 app.exec_()
251
263
252
264
253 if __name__ == '__main__':
265 if __name__ == '__main__':
254 main()
266 main()
@@ -1,567 +1,580 b''
1 """A ZMQ-based subclass of InteractiveShell.
1 """A ZMQ-based subclass of InteractiveShell.
2
2
3 This code is meant to ease the refactoring of the base InteractiveShell into
3 This code is meant to ease the refactoring of the base InteractiveShell into
4 something with a cleaner architecture for 2-process use, without actually
4 something with a cleaner architecture for 2-process use, without actually
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 we subclass and override what we want to fix. Once this is working well, we
6 we subclass and override what we want to fix. Once this is working well, we
7 can go back to the base class and refactor the code for a cleaner inheritance
7 can go back to the base class and refactor the code for a cleaner inheritance
8 implementation that doesn't rely on so much monkeypatching.
8 implementation that doesn't rely on so much monkeypatching.
9
9
10 But this lets us maintain a fully working IPython as we develop the new
10 But this lets us maintain a fully working IPython as we develop the new
11 machinery. This should thus be thought of as scaffolding.
11 machinery. This should thus be thought of as scaffolding.
12 """
12 """
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import inspect
19 import inspect
20 import os
20 import os
21 import re
21 import re
22
22
23 # Our own
23 # Our own
24 from IPython.core.interactiveshell import (
24 from IPython.core.interactiveshell import (
25 InteractiveShell, InteractiveShellABC
25 InteractiveShell, InteractiveShellABC
26 )
26 )
27 from IPython.core import page
27 from IPython.core import page
28 from IPython.core.displayhook import DisplayHook
28 from IPython.core.displayhook import DisplayHook
29 from IPython.core.macro import Macro
29 from IPython.core.macro import Macro
30 from IPython.core.payloadpage import install_payload_page
30 from IPython.core.payloadpage import install_payload_page
31 from IPython.utils import io
31 from IPython.utils import io
32 from IPython.utils.path import get_py_filename
32 from IPython.utils.path import get_py_filename
33 from IPython.utils.text import StringTypes
33 from IPython.utils.text import StringTypes
34 from IPython.utils.traitlets import Instance, Type, Dict
34 from IPython.utils.traitlets import Instance, Type, Dict
35 from IPython.utils.warn import warn
35 from IPython.utils.warn import warn
36 from IPython.zmq.session import extract_header
36 from IPython.zmq.session import extract_header
37 from session import Session
37 from session import Session
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Globals and side-effects
40 # Globals and side-effects
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 # Install the payload version of page.
43 # Install the payload version of page.
44 install_payload_page()
44 install_payload_page()
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Functions and classes
47 # Functions and classes
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 class ZMQDisplayHook(DisplayHook):
50 class ZMQDisplayHook(DisplayHook):
51
51
52 session = Instance(Session)
52 session = Instance(Session)
53 pub_socket = Instance('zmq.Socket')
53 pub_socket = Instance('zmq.Socket')
54 parent_header = Dict({})
54 parent_header = Dict({})
55
55
56 def set_parent(self, parent):
56 def set_parent(self, parent):
57 """Set the parent for outbound messages."""
57 """Set the parent for outbound messages."""
58 self.parent_header = extract_header(parent)
58 self.parent_header = extract_header(parent)
59
59
60 def start_displayhook(self):
60 def start_displayhook(self):
61 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
61 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
62
62
63 def write_output_prompt(self):
63 def write_output_prompt(self):
64 """Write the output prompt."""
64 """Write the output prompt."""
65 if self.do_full_cache:
65 if self.do_full_cache:
66 self.msg['content']['execution_count'] = self.prompt_count
66 self.msg['content']['execution_count'] = self.prompt_count
67
67
68 def write_result_repr(self, result_repr):
68 def write_result_repr(self, result_repr):
69 self.msg['content']['data'] = result_repr
69 self.msg['content']['data'] = result_repr
70
70
71 def finish_displayhook(self):
71 def finish_displayhook(self):
72 """Finish up all displayhook activities."""
72 """Finish up all displayhook activities."""
73 self.pub_socket.send_json(self.msg)
73 self.pub_socket.send_json(self.msg)
74 self.msg = None
74 self.msg = None
75
75
76
76
77 class ZMQInteractiveShell(InteractiveShell):
77 class ZMQInteractiveShell(InteractiveShell):
78 """A subclass of InteractiveShell for ZMQ."""
78 """A subclass of InteractiveShell for ZMQ."""
79
79
80 displayhook_class = Type(ZMQDisplayHook)
80 displayhook_class = Type(ZMQDisplayHook)
81 keepkernel = None
81
82
82 def init_environment(self):
83 def init_environment(self):
83 """Configure the user's environment.
84 """Configure the user's environment.
84
85
85 """
86 """
86 env = os.environ
87 env = os.environ
87 # These two ensure 'ls' produces nice coloring on BSD-derived systems
88 # These two ensure 'ls' produces nice coloring on BSD-derived systems
88 env['TERM'] = 'xterm-color'
89 env['TERM'] = 'xterm-color'
89 env['CLICOLOR'] = '1'
90 env['CLICOLOR'] = '1'
90 # Since normal pagers don't work at all (over pexpect we don't have
91 # Since normal pagers don't work at all (over pexpect we don't have
91 # single-key control of the subprocess), try to disable paging in
92 # single-key control of the subprocess), try to disable paging in
92 # subprocesses as much as possible.
93 # subprocesses as much as possible.
93 env['PAGER'] = 'cat'
94 env['PAGER'] = 'cat'
94 env['GIT_PAGER'] = 'cat'
95 env['GIT_PAGER'] = 'cat'
95
96
96 def auto_rewrite_input(self, cmd):
97 def auto_rewrite_input(self, cmd):
97 """Called to show the auto-rewritten input for autocall and friends.
98 """Called to show the auto-rewritten input for autocall and friends.
98
99
99 FIXME: this payload is currently not correctly processed by the
100 FIXME: this payload is currently not correctly processed by the
100 frontend.
101 frontend.
101 """
102 """
102 new = self.displayhook.prompt1.auto_rewrite() + cmd
103 new = self.displayhook.prompt1.auto_rewrite() + cmd
103 payload = dict(
104 payload = dict(
104 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
105 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
105 transformed_input=new,
106 transformed_input=new,
106 )
107 )
107 self.payload_manager.write_payload(payload)
108 self.payload_manager.write_payload(payload)
108
109
109 def ask_exit(self):
110 def ask_exit(self):
110 """Engage the exit actions."""
111 """Engage the exit actions."""
111 payload = dict(
112 payload = dict(
112 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
113 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
113 exit=True,
114 exit=True,
115 keepkernel=self.keepkernel,
114 )
116 )
115 self.payload_manager.write_payload(payload)
117 self.payload_manager.write_payload(payload)
116
118
117 def _showtraceback(self, etype, evalue, stb):
119 def _showtraceback(self, etype, evalue, stb):
118
120
119 exc_content = {
121 exc_content = {
120 u'traceback' : stb,
122 u'traceback' : stb,
121 u'ename' : unicode(etype.__name__),
123 u'ename' : unicode(etype.__name__),
122 u'evalue' : unicode(evalue)
124 u'evalue' : unicode(evalue)
123 }
125 }
124
126
125 dh = self.displayhook
127 dh = self.displayhook
126 exc_msg = dh.session.msg(u'pyerr', exc_content, dh.parent_header)
128 exc_msg = dh.session.msg(u'pyerr', exc_content, dh.parent_header)
127 # Send exception info over pub socket for other clients than the caller
129 # Send exception info over pub socket for other clients than the caller
128 # to pick up
130 # to pick up
129 dh.pub_socket.send_json(exc_msg)
131 dh.pub_socket.send_json(exc_msg)
130
132
131 # FIXME - Hack: store exception info in shell object. Right now, the
133 # FIXME - Hack: store exception info in shell object. Right now, the
132 # caller is reading this info after the fact, we need to fix this logic
134 # caller is reading this info after the fact, we need to fix this logic
133 # to remove this hack. Even uglier, we need to store the error status
135 # to remove this hack. Even uglier, we need to store the error status
134 # here, because in the main loop, the logic that sets it is being
136 # here, because in the main loop, the logic that sets it is being
135 # skipped because runlines swallows the exceptions.
137 # skipped because runlines swallows the exceptions.
136 exc_content[u'status'] = u'error'
138 exc_content[u'status'] = u'error'
137 self._reply_content = exc_content
139 self._reply_content = exc_content
138 # /FIXME
140 # /FIXME
139
141
140 return exc_content
142 return exc_content
141
143
142 #------------------------------------------------------------------------
144 #------------------------------------------------------------------------
143 # Magic overrides
145 # Magic overrides
144 #------------------------------------------------------------------------
146 #------------------------------------------------------------------------
145 # Once the base class stops inheriting from magic, this code needs to be
147 # Once the base class stops inheriting from magic, this code needs to be
146 # moved into a separate machinery as well. For now, at least isolate here
148 # moved into a separate machinery as well. For now, at least isolate here
147 # the magics which this class needs to implement differently from the base
149 # the magics which this class needs to implement differently from the base
148 # class, or that are unique to it.
150 # class, or that are unique to it.
149
151
150 def magic_doctest_mode(self,parameter_s=''):
152 def magic_doctest_mode(self,parameter_s=''):
151 """Toggle doctest mode on and off.
153 """Toggle doctest mode on and off.
152
154
153 This mode is intended to make IPython behave as much as possible like a
155 This mode is intended to make IPython behave as much as possible like a
154 plain Python shell, from the perspective of how its prompts, exceptions
156 plain Python shell, from the perspective of how its prompts, exceptions
155 and output look. This makes it easy to copy and paste parts of a
157 and output look. This makes it easy to copy and paste parts of a
156 session into doctests. It does so by:
158 session into doctests. It does so by:
157
159
158 - Changing the prompts to the classic ``>>>`` ones.
160 - Changing the prompts to the classic ``>>>`` ones.
159 - Changing the exception reporting mode to 'Plain'.
161 - Changing the exception reporting mode to 'Plain'.
160 - Disabling pretty-printing of output.
162 - Disabling pretty-printing of output.
161
163
162 Note that IPython also supports the pasting of code snippets that have
164 Note that IPython also supports the pasting of code snippets that have
163 leading '>>>' and '...' prompts in them. This means that you can paste
165 leading '>>>' and '...' prompts in them. This means that you can paste
164 doctests from files or docstrings (even if they have leading
166 doctests from files or docstrings (even if they have leading
165 whitespace), and the code will execute correctly. You can then use
167 whitespace), and the code will execute correctly. You can then use
166 '%history -t' to see the translated history; this will give you the
168 '%history -t' to see the translated history; this will give you the
167 input after removal of all the leading prompts and whitespace, which
169 input after removal of all the leading prompts and whitespace, which
168 can be pasted back into an editor.
170 can be pasted back into an editor.
169
171
170 With these features, you can switch into this mode easily whenever you
172 With these features, you can switch into this mode easily whenever you
171 need to do testing and changes to doctests, without having to leave
173 need to do testing and changes to doctests, without having to leave
172 your existing IPython session.
174 your existing IPython session.
173 """
175 """
174
176
175 from IPython.utils.ipstruct import Struct
177 from IPython.utils.ipstruct import Struct
176
178
177 # Shorthands
179 # Shorthands
178 shell = self.shell
180 shell = self.shell
179 # dstore is a data store kept in the instance metadata bag to track any
181 # dstore is a data store kept in the instance metadata bag to track any
180 # changes we make, so we can undo them later.
182 # changes we make, so we can undo them later.
181 dstore = shell.meta.setdefault('doctest_mode', Struct())
183 dstore = shell.meta.setdefault('doctest_mode', Struct())
182 save_dstore = dstore.setdefault
184 save_dstore = dstore.setdefault
183
185
184 # save a few values we'll need to recover later
186 # save a few values we'll need to recover later
185 mode = save_dstore('mode', False)
187 mode = save_dstore('mode', False)
186 save_dstore('rc_pprint', shell.pprint)
188 save_dstore('rc_pprint', shell.pprint)
187 save_dstore('xmode', shell.InteractiveTB.mode)
189 save_dstore('xmode', shell.InteractiveTB.mode)
188
190
189 if mode == False:
191 if mode == False:
190 # turn on
192 # turn on
191 shell.pprint = False
193 shell.pprint = False
192 shell.magic_xmode('Plain')
194 shell.magic_xmode('Plain')
193 else:
195 else:
194 # turn off
196 # turn off
195 shell.pprint = dstore.rc_pprint
197 shell.pprint = dstore.rc_pprint
196 shell.magic_xmode(dstore.xmode)
198 shell.magic_xmode(dstore.xmode)
197
199
198 # Store new mode and inform on console
200 # Store new mode and inform on console
199 dstore.mode = bool(1-int(mode))
201 dstore.mode = bool(1-int(mode))
200 mode_label = ['OFF','ON'][dstore.mode]
202 mode_label = ['OFF','ON'][dstore.mode]
201 print('Doctest mode is:', mode_label)
203 print('Doctest mode is:', mode_label)
202
204
203 # Send the payload back so that clients can modify their prompt display
205 # Send the payload back so that clients can modify their prompt display
204 payload = dict(
206 payload = dict(
205 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode',
207 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode',
206 mode=dstore.mode)
208 mode=dstore.mode)
207 self.payload_manager.write_payload(payload)
209 self.payload_manager.write_payload(payload)
208
210
209 def magic_edit(self,parameter_s='',last_call=['','']):
211 def magic_edit(self,parameter_s='',last_call=['','']):
210 """Bring up an editor and execute the resulting code.
212 """Bring up an editor and execute the resulting code.
211
213
212 Usage:
214 Usage:
213 %edit [options] [args]
215 %edit [options] [args]
214
216
215 %edit runs IPython's editor hook. The default version of this hook is
217 %edit runs IPython's editor hook. The default version of this hook is
216 set to call the __IPYTHON__.rc.editor command. This is read from your
218 set to call the __IPYTHON__.rc.editor command. This is read from your
217 environment variable $EDITOR. If this isn't found, it will default to
219 environment variable $EDITOR. If this isn't found, it will default to
218 vi under Linux/Unix and to notepad under Windows. See the end of this
220 vi under Linux/Unix and to notepad under Windows. See the end of this
219 docstring for how to change the editor hook.
221 docstring for how to change the editor hook.
220
222
221 You can also set the value of this editor via the command line option
223 You can also set the value of this editor via the command line option
222 '-editor' or in your ipythonrc file. This is useful if you wish to use
224 '-editor' or in your ipythonrc file. This is useful if you wish to use
223 specifically for IPython an editor different from your typical default
225 specifically for IPython an editor different from your typical default
224 (and for Windows users who typically don't set environment variables).
226 (and for Windows users who typically don't set environment variables).
225
227
226 This command allows you to conveniently edit multi-line code right in
228 This command allows you to conveniently edit multi-line code right in
227 your IPython session.
229 your IPython session.
228
230
229 If called without arguments, %edit opens up an empty editor with a
231 If called without arguments, %edit opens up an empty editor with a
230 temporary file and will execute the contents of this file when you
232 temporary file and will execute the contents of this file when you
231 close it (don't forget to save it!).
233 close it (don't forget to save it!).
232
234
233
235
234 Options:
236 Options:
235
237
236 -n <number>: open the editor at a specified line number. By default,
238 -n <number>: open the editor at a specified line number. By default,
237 the IPython editor hook uses the unix syntax 'editor +N filename', but
239 the IPython editor hook uses the unix syntax 'editor +N filename', but
238 you can configure this by providing your own modified hook if your
240 you can configure this by providing your own modified hook if your
239 favorite editor supports line-number specifications with a different
241 favorite editor supports line-number specifications with a different
240 syntax.
242 syntax.
241
243
242 -p: this will call the editor with the same data as the previous time
244 -p: this will call the editor with the same data as the previous time
243 it was used, regardless of how long ago (in your current session) it
245 it was used, regardless of how long ago (in your current session) it
244 was.
246 was.
245
247
246 -r: use 'raw' input. This option only applies to input taken from the
248 -r: use 'raw' input. This option only applies to input taken from the
247 user's history. By default, the 'processed' history is used, so that
249 user's history. By default, the 'processed' history is used, so that
248 magics are loaded in their transformed version to valid Python. If
250 magics are loaded in their transformed version to valid Python. If
249 this option is given, the raw input as typed as the command line is
251 this option is given, the raw input as typed as the command line is
250 used instead. When you exit the editor, it will be executed by
252 used instead. When you exit the editor, it will be executed by
251 IPython's own processor.
253 IPython's own processor.
252
254
253 -x: do not execute the edited code immediately upon exit. This is
255 -x: do not execute the edited code immediately upon exit. This is
254 mainly useful if you are editing programs which need to be called with
256 mainly useful if you are editing programs which need to be called with
255 command line arguments, which you can then do using %run.
257 command line arguments, which you can then do using %run.
256
258
257
259
258 Arguments:
260 Arguments:
259
261
260 If arguments are given, the following possibilites exist:
262 If arguments are given, the following possibilites exist:
261
263
262 - The arguments are numbers or pairs of colon-separated numbers (like
264 - The arguments are numbers or pairs of colon-separated numbers (like
263 1 4:8 9). These are interpreted as lines of previous input to be
265 1 4:8 9). These are interpreted as lines of previous input to be
264 loaded into the editor. The syntax is the same of the %macro command.
266 loaded into the editor. The syntax is the same of the %macro command.
265
267
266 - If the argument doesn't start with a number, it is evaluated as a
268 - If the argument doesn't start with a number, it is evaluated as a
267 variable and its contents loaded into the editor. You can thus edit
269 variable and its contents loaded into the editor. You can thus edit
268 any string which contains python code (including the result of
270 any string which contains python code (including the result of
269 previous edits).
271 previous edits).
270
272
271 - If the argument is the name of an object (other than a string),
273 - If the argument is the name of an object (other than a string),
272 IPython will try to locate the file where it was defined and open the
274 IPython will try to locate the file where it was defined and open the
273 editor at the point where it is defined. You can use `%edit function`
275 editor at the point where it is defined. You can use `%edit function`
274 to load an editor exactly at the point where 'function' is defined,
276 to load an editor exactly at the point where 'function' is defined,
275 edit it and have the file be executed automatically.
277 edit it and have the file be executed automatically.
276
278
277 If the object is a macro (see %macro for details), this opens up your
279 If the object is a macro (see %macro for details), this opens up your
278 specified editor with a temporary file containing the macro's data.
280 specified editor with a temporary file containing the macro's data.
279 Upon exit, the macro is reloaded with the contents of the file.
281 Upon exit, the macro is reloaded with the contents of the file.
280
282
281 Note: opening at an exact line is only supported under Unix, and some
283 Note: opening at an exact line is only supported under Unix, and some
282 editors (like kedit and gedit up to Gnome 2.8) do not understand the
284 editors (like kedit and gedit up to Gnome 2.8) do not understand the
283 '+NUMBER' parameter necessary for this feature. Good editors like
285 '+NUMBER' parameter necessary for this feature. Good editors like
284 (X)Emacs, vi, jed, pico and joe all do.
286 (X)Emacs, vi, jed, pico and joe all do.
285
287
286 - If the argument is not found as a variable, IPython will look for a
288 - If the argument is not found as a variable, IPython will look for a
287 file with that name (adding .py if necessary) and load it into the
289 file with that name (adding .py if necessary) and load it into the
288 editor. It will execute its contents with execfile() when you exit,
290 editor. It will execute its contents with execfile() when you exit,
289 loading any code in the file into your interactive namespace.
291 loading any code in the file into your interactive namespace.
290
292
291 After executing your code, %edit will return as output the code you
293 After executing your code, %edit will return as output the code you
292 typed in the editor (except when it was an existing file). This way
294 typed in the editor (except when it was an existing file). This way
293 you can reload the code in further invocations of %edit as a variable,
295 you can reload the code in further invocations of %edit as a variable,
294 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
296 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
295 the output.
297 the output.
296
298
297 Note that %edit is also available through the alias %ed.
299 Note that %edit is also available through the alias %ed.
298
300
299 This is an example of creating a simple function inside the editor and
301 This is an example of creating a simple function inside the editor and
300 then modifying it. First, start up the editor:
302 then modifying it. First, start up the editor:
301
303
302 In [1]: ed
304 In [1]: ed
303 Editing... done. Executing edited code...
305 Editing... done. Executing edited code...
304 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
306 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
305
307
306 We can then call the function foo():
308 We can then call the function foo():
307
309
308 In [2]: foo()
310 In [2]: foo()
309 foo() was defined in an editing session
311 foo() was defined in an editing session
310
312
311 Now we edit foo. IPython automatically loads the editor with the
313 Now we edit foo. IPython automatically loads the editor with the
312 (temporary) file where foo() was previously defined:
314 (temporary) file where foo() was previously defined:
313
315
314 In [3]: ed foo
316 In [3]: ed foo
315 Editing... done. Executing edited code...
317 Editing... done. Executing edited code...
316
318
317 And if we call foo() again we get the modified version:
319 And if we call foo() again we get the modified version:
318
320
319 In [4]: foo()
321 In [4]: foo()
320 foo() has now been changed!
322 foo() has now been changed!
321
323
322 Here is an example of how to edit a code snippet successive
324 Here is an example of how to edit a code snippet successive
323 times. First we call the editor:
325 times. First we call the editor:
324
326
325 In [5]: ed
327 In [5]: ed
326 Editing... done. Executing edited code...
328 Editing... done. Executing edited code...
327 hello
329 hello
328 Out[5]: "print 'hello'n"
330 Out[5]: "print 'hello'n"
329
331
330 Now we call it again with the previous output (stored in _):
332 Now we call it again with the previous output (stored in _):
331
333
332 In [6]: ed _
334 In [6]: ed _
333 Editing... done. Executing edited code...
335 Editing... done. Executing edited code...
334 hello world
336 hello world
335 Out[6]: "print 'hello world'n"
337 Out[6]: "print 'hello world'n"
336
338
337 Now we call it with the output #8 (stored in _8, also as Out[8]):
339 Now we call it with the output #8 (stored in _8, also as Out[8]):
338
340
339 In [7]: ed _8
341 In [7]: ed _8
340 Editing... done. Executing edited code...
342 Editing... done. Executing edited code...
341 hello again
343 hello again
342 Out[7]: "print 'hello again'n"
344 Out[7]: "print 'hello again'n"
343
345
344
346
345 Changing the default editor hook:
347 Changing the default editor hook:
346
348
347 If you wish to write your own editor hook, you can put it in a
349 If you wish to write your own editor hook, you can put it in a
348 configuration file which you load at startup time. The default hook
350 configuration file which you load at startup time. The default hook
349 is defined in the IPython.core.hooks module, and you can use that as a
351 is defined in the IPython.core.hooks module, and you can use that as a
350 starting example for further modifications. That file also has
352 starting example for further modifications. That file also has
351 general instructions on how to set a new hook for use once you've
353 general instructions on how to set a new hook for use once you've
352 defined it."""
354 defined it."""
353
355
354 # FIXME: This function has become a convoluted mess. It needs a
356 # FIXME: This function has become a convoluted mess. It needs a
355 # ground-up rewrite with clean, simple logic.
357 # ground-up rewrite with clean, simple logic.
356
358
357 def make_filename(arg):
359 def make_filename(arg):
358 "Make a filename from the given args"
360 "Make a filename from the given args"
359 try:
361 try:
360 filename = get_py_filename(arg)
362 filename = get_py_filename(arg)
361 except IOError:
363 except IOError:
362 if args.endswith('.py'):
364 if args.endswith('.py'):
363 filename = arg
365 filename = arg
364 else:
366 else:
365 filename = None
367 filename = None
366 return filename
368 return filename
367
369
368 # custom exceptions
370 # custom exceptions
369 class DataIsObject(Exception): pass
371 class DataIsObject(Exception): pass
370
372
371 opts,args = self.parse_options(parameter_s,'prn:')
373 opts,args = self.parse_options(parameter_s,'prn:')
372 # Set a few locals from the options for convenience:
374 # Set a few locals from the options for convenience:
373 opts_p = opts.has_key('p')
375 opts_p = opts.has_key('p')
374 opts_r = opts.has_key('r')
376 opts_r = opts.has_key('r')
375
377
376 # Default line number value
378 # Default line number value
377 lineno = opts.get('n',None)
379 lineno = opts.get('n',None)
378 if lineno is not None:
380 if lineno is not None:
379 try:
381 try:
380 lineno = int(lineno)
382 lineno = int(lineno)
381 except:
383 except:
382 warn("The -n argument must be an integer.")
384 warn("The -n argument must be an integer.")
383 return
385 return
384
386
385 if opts_p:
387 if opts_p:
386 args = '_%s' % last_call[0]
388 args = '_%s' % last_call[0]
387 if not self.shell.user_ns.has_key(args):
389 if not self.shell.user_ns.has_key(args):
388 args = last_call[1]
390 args = last_call[1]
389
391
390 # use last_call to remember the state of the previous call, but don't
392 # use last_call to remember the state of the previous call, but don't
391 # let it be clobbered by successive '-p' calls.
393 # let it be clobbered by successive '-p' calls.
392 try:
394 try:
393 last_call[0] = self.shell.displayhook.prompt_count
395 last_call[0] = self.shell.displayhook.prompt_count
394 if not opts_p:
396 if not opts_p:
395 last_call[1] = parameter_s
397 last_call[1] = parameter_s
396 except:
398 except:
397 pass
399 pass
398
400
399 # by default this is done with temp files, except when the given
401 # by default this is done with temp files, except when the given
400 # arg is a filename
402 # arg is a filename
401 use_temp = 1
403 use_temp = 1
402
404
403 if re.match(r'\d',args):
405 if re.match(r'\d',args):
404 # Mode where user specifies ranges of lines, like in %macro.
406 # Mode where user specifies ranges of lines, like in %macro.
405 # This means that you can't edit files whose names begin with
407 # This means that you can't edit files whose names begin with
406 # numbers this way. Tough.
408 # numbers this way. Tough.
407 ranges = args.split()
409 ranges = args.split()
408 data = ''.join(self.extract_input_slices(ranges,opts_r))
410 data = ''.join(self.extract_input_slices(ranges,opts_r))
409 elif args.endswith('.py'):
411 elif args.endswith('.py'):
410 filename = make_filename(args)
412 filename = make_filename(args)
411 data = ''
413 data = ''
412 use_temp = 0
414 use_temp = 0
413 elif args:
415 elif args:
414 try:
416 try:
415 # Load the parameter given as a variable. If not a string,
417 # Load the parameter given as a variable. If not a string,
416 # process it as an object instead (below)
418 # process it as an object instead (below)
417
419
418 #print '*** args',args,'type',type(args) # dbg
420 #print '*** args',args,'type',type(args) # dbg
419 data = eval(args,self.shell.user_ns)
421 data = eval(args,self.shell.user_ns)
420 if not type(data) in StringTypes:
422 if not type(data) in StringTypes:
421 raise DataIsObject
423 raise DataIsObject
422
424
423 except (NameError,SyntaxError):
425 except (NameError,SyntaxError):
424 # given argument is not a variable, try as a filename
426 # given argument is not a variable, try as a filename
425 filename = make_filename(args)
427 filename = make_filename(args)
426 if filename is None:
428 if filename is None:
427 warn("Argument given (%s) can't be found as a variable "
429 warn("Argument given (%s) can't be found as a variable "
428 "or as a filename." % args)
430 "or as a filename." % args)
429 return
431 return
430
432
431 data = ''
433 data = ''
432 use_temp = 0
434 use_temp = 0
433 except DataIsObject:
435 except DataIsObject:
434
436
435 # macros have a special edit function
437 # macros have a special edit function
436 if isinstance(data,Macro):
438 if isinstance(data,Macro):
437 self._edit_macro(args,data)
439 self._edit_macro(args,data)
438 return
440 return
439
441
440 # For objects, try to edit the file where they are defined
442 # For objects, try to edit the file where they are defined
441 try:
443 try:
442 filename = inspect.getabsfile(data)
444 filename = inspect.getabsfile(data)
443 if 'fakemodule' in filename.lower() and inspect.isclass(data):
445 if 'fakemodule' in filename.lower() and inspect.isclass(data):
444 # class created by %edit? Try to find source
446 # class created by %edit? Try to find source
445 # by looking for method definitions instead, the
447 # by looking for method definitions instead, the
446 # __module__ in those classes is FakeModule.
448 # __module__ in those classes is FakeModule.
447 attrs = [getattr(data, aname) for aname in dir(data)]
449 attrs = [getattr(data, aname) for aname in dir(data)]
448 for attr in attrs:
450 for attr in attrs:
449 if not inspect.ismethod(attr):
451 if not inspect.ismethod(attr):
450 continue
452 continue
451 filename = inspect.getabsfile(attr)
453 filename = inspect.getabsfile(attr)
452 if filename and 'fakemodule' not in filename.lower():
454 if filename and 'fakemodule' not in filename.lower():
453 # change the attribute to be the edit target instead
455 # change the attribute to be the edit target instead
454 data = attr
456 data = attr
455 break
457 break
456
458
457 datafile = 1
459 datafile = 1
458 except TypeError:
460 except TypeError:
459 filename = make_filename(args)
461 filename = make_filename(args)
460 datafile = 1
462 datafile = 1
461 warn('Could not find file where `%s` is defined.\n'
463 warn('Could not find file where `%s` is defined.\n'
462 'Opening a file named `%s`' % (args,filename))
464 'Opening a file named `%s`' % (args,filename))
463 # Now, make sure we can actually read the source (if it was in
465 # Now, make sure we can actually read the source (if it was in
464 # a temp file it's gone by now).
466 # a temp file it's gone by now).
465 if datafile:
467 if datafile:
466 try:
468 try:
467 if lineno is None:
469 if lineno is None:
468 lineno = inspect.getsourcelines(data)[1]
470 lineno = inspect.getsourcelines(data)[1]
469 except IOError:
471 except IOError:
470 filename = make_filename(args)
472 filename = make_filename(args)
471 if filename is None:
473 if filename is None:
472 warn('The file `%s` where `%s` was defined cannot '
474 warn('The file `%s` where `%s` was defined cannot '
473 'be read.' % (filename,data))
475 'be read.' % (filename,data))
474 return
476 return
475 use_temp = 0
477 use_temp = 0
476 else:
478 else:
477 data = ''
479 data = ''
478
480
479 if use_temp:
481 if use_temp:
480 filename = self.shell.mktempfile(data)
482 filename = self.shell.mktempfile(data)
481 print('IPython will make a temporary file named:', filename)
483 print('IPython will make a temporary file named:', filename)
482
484
483 # Make sure we send to the client an absolute path, in case the working
485 # Make sure we send to the client an absolute path, in case the working
484 # directory of client and kernel don't match
486 # directory of client and kernel don't match
485 filename = os.path.abspath(filename)
487 filename = os.path.abspath(filename)
486
488
487 payload = {
489 payload = {
488 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
490 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
489 'filename' : filename,
491 'filename' : filename,
490 'line_number' : lineno
492 'line_number' : lineno
491 }
493 }
492 self.payload_manager.write_payload(payload)
494 self.payload_manager.write_payload(payload)
493
495
494 def magic_gui(self, *args, **kwargs):
496 def magic_gui(self, *args, **kwargs):
495 raise NotImplementedError(
497 raise NotImplementedError(
496 'GUI support must be enabled in command line options.')
498 'GUI support must be enabled in command line options.')
497
499
498 def magic_pylab(self, *args, **kwargs):
500 def magic_pylab(self, *args, **kwargs):
499 raise NotImplementedError(
501 raise NotImplementedError(
500 'pylab support must be enabled in command line options.')
502 'pylab support must be enabled in command line options.')
501
503
502 # A few magics that are adapted to the specifics of using pexpect and a
504 # A few magics that are adapted to the specifics of using pexpect and a
503 # remote terminal
505 # remote terminal
504
506
505 def magic_clear(self, arg_s):
507 def magic_clear(self, arg_s):
506 """Clear the terminal."""
508 """Clear the terminal."""
507 if os.name == 'posix':
509 if os.name == 'posix':
508 self.shell.system("clear")
510 self.shell.system("clear")
509 else:
511 else:
510 self.shell.system("cls")
512 self.shell.system("cls")
511
513
512 if os.name == 'nt':
514 if os.name == 'nt':
513 # This is the usual name in windows
515 # This is the usual name in windows
514 magic_cls = magic_clear
516 magic_cls = magic_clear
515
517
516 # Terminal pagers won't work over pexpect, but we do have our own pager
518 # Terminal pagers won't work over pexpect, but we do have our own pager
517
519
518 def magic_less(self, arg_s):
520 def magic_less(self, arg_s):
519 """Show a file through the pager.
521 """Show a file through the pager.
520
522
521 Files ending in .py are syntax-highlighted."""
523 Files ending in .py are syntax-highlighted."""
522 cont = open(arg_s).read()
524 cont = open(arg_s).read()
523 if arg_s.endswith('.py'):
525 if arg_s.endswith('.py'):
524 cont = self.shell.pycolorize(cont)
526 cont = self.shell.pycolorize(cont)
525 page.page(cont)
527 page.page(cont)
526
528
527 magic_more = magic_less
529 magic_more = magic_less
528
530
529 # Man calls a pager, so we also need to redefine it
531 # Man calls a pager, so we also need to redefine it
530 if os.name == 'posix':
532 if os.name == 'posix':
531 def magic_man(self, arg_s):
533 def magic_man(self, arg_s):
532 """Find the man page for the given command and display in pager."""
534 """Find the man page for the given command and display in pager."""
533 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
535 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
534 split=False))
536 split=False))
535
537
536 # FIXME: this is specific to the GUI, so we should let the gui app load
538 # FIXME: this is specific to the GUI, so we should let the gui app load
537 # magics at startup that are only for the gui. Once the gui app has proper
539 # magics at startup that are only for the gui. Once the gui app has proper
538 # profile and configuration management, we can have it initialize a kernel
540 # profile and configuration management, we can have it initialize a kernel
539 # with a special config file that provides these.
541 # with a special config file that provides these.
540 def magic_guiref(self, arg_s):
542 def magic_guiref(self, arg_s):
541 """Show a basic reference about the GUI console."""
543 """Show a basic reference about the GUI console."""
542 from IPython.core.usage import gui_reference
544 from IPython.core.usage import gui_reference
543 page.page(gui_reference, auto_html=True)
545 page.page(gui_reference, auto_html=True)
544
546
545 def magic_loadpy(self, arg_s):
547 def magic_loadpy(self, arg_s):
546 """Load a .py python script into the GUI console.
548 """Load a .py python script into the GUI console.
547
549
548 This magic command can either take a local filename or a url::
550 This magic command can either take a local filename or a url::
549
551
550 %loadpy myscript.py
552 %loadpy myscript.py
551 %loadpy http://www.example.com/myscript.py
553 %loadpy http://www.example.com/myscript.py
552 """
554 """
553 if not arg_s.endswith('.py'):
555 if not arg_s.endswith('.py'):
554 raise ValueError('%%load only works with .py files: %s' % arg_s)
556 raise ValueError('%%load only works with .py files: %s' % arg_s)
555 if arg_s.startswith('http'):
557 if arg_s.startswith('http'):
556 import urllib2
558 import urllib2
557 response = urllib2.urlopen(arg_s)
559 response = urllib2.urlopen(arg_s)
558 content = response.read()
560 content = response.read()
559 else:
561 else:
560 content = open(arg_s).read()
562 content = open(arg_s).read()
561 payload = dict(
563 payload = dict(
562 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_loadpy',
564 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_loadpy',
563 text=content
565 text=content
564 )
566 )
565 self.payload_manager.write_payload(payload)
567 self.payload_manager.write_payload(payload)
568
569 def magic_Exit(self, parameter_s=''):
570 """Exit IPython. If the -k option is provided, the kernel will be left
571 running. Otherwise, it will shutdown without prompting.
572 """
573 opts,args = self.parse_options(parameter_s,'k')
574 self.shell.keepkernel = opts.has_key('k')
575 self.shell.ask_exit()
576
577 # Add aliases as magics so all common forms work: exit, quit, Exit, Quit.
578 magic_exit = magic_quit = magic_Quit = magic_Exit
566
579
567 InteractiveShellABC.register(ZMQInteractiveShell)
580 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now