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