##// END OF EJS Templates
split qtconsole's connection-file search into lib.kernel...
MinRK -
Show More
@@ -1,606 +1,591 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib imports
20 import glob
21 20 import os
22 21 import signal
23 22 import sys
24 23
25 24 # System library imports
26 25 from IPython.external.qt import QtGui
27 26 from pygments.styles import get_all_styles
28 27 from zmq.utils import jsonapi as json
29 28
30 29 # Local imports
31 30 from IPython.config.application import boolean_flag
32 31 from IPython.core.application import BaseIPythonApplication
33 32 from IPython.core.profiledir import ProfileDir
34 from IPython.lib.kernel import tunnel_to_kernel
33 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
35 34 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
36 35 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
37 36 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
38 37 from IPython.frontend.qt.console import styles
39 38 from IPython.frontend.qt.kernelmanager import QtKernelManager
40 39 from IPython.utils.path import filefind
41 40 from IPython.utils.py3compat import str_to_bytes
42 41 from IPython.utils.traitlets import (
43 42 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
44 43 )
45 44 from IPython.zmq.ipkernel import (
46 45 flags as ipkernel_flags,
47 46 aliases as ipkernel_aliases,
48 47 IPKernelApp
49 48 )
50 49 from IPython.zmq.session import Session, default_secure
51 50 from IPython.zmq.zmqshell import ZMQInteractiveShell
52 51
53 52
54 53 #-----------------------------------------------------------------------------
55 54 # Network Constants
56 55 #-----------------------------------------------------------------------------
57 56
58 57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59 58
60 59 #-----------------------------------------------------------------------------
61 60 # Globals
62 61 #-----------------------------------------------------------------------------
63 62
64 63 _examples = """
65 64 ipython qtconsole # start the qtconsole
66 65 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 66 """
68 67
69 68 #-----------------------------------------------------------------------------
70 69 # Classes
71 70 #-----------------------------------------------------------------------------
72 71
73 72 class MainWindow(QtGui.QMainWindow):
74 73
75 74 #---------------------------------------------------------------------------
76 75 # 'object' interface
77 76 #---------------------------------------------------------------------------
78 77
79 78 def __init__(self, app, frontend, existing=False, may_close=True,
80 79 confirm_exit=True):
81 80 """ Create a MainWindow for the specified FrontendWidget.
82 81
83 82 The app is passed as an argument to allow for different
84 83 closing behavior depending on whether we are the Kernel's parent.
85 84
86 85 If existing is True, then this Console does not own the Kernel.
87 86
88 87 If may_close is True, then this Console is permitted to close the kernel
89 88 """
90 89 super(MainWindow, self).__init__()
91 90 self._app = app
92 91 self._frontend = frontend
93 92 self._existing = existing
94 93 if existing:
95 94 self._may_close = may_close
96 95 else:
97 96 self._may_close = True
98 97 self._frontend.exit_requested.connect(self.close)
99 98 self._confirm_exit = confirm_exit
100 99 self.setCentralWidget(frontend)
101 100
102 101 #---------------------------------------------------------------------------
103 102 # QWidget interface
104 103 #---------------------------------------------------------------------------
105 104
106 105 def closeEvent(self, event):
107 106 """ Close the window and the kernel (if necessary).
108 107
109 108 This will prompt the user if they are finished with the kernel, and if
110 109 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
111 110 it closes without prompt.
112 111 """
113 112 keepkernel = None #Use the prompt by default
114 113 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
115 114 keepkernel = self._frontend._keep_kernel_on_exit
116 115
117 116 kernel_manager = self._frontend.kernel_manager
118 117
119 118 if keepkernel is None and not self._confirm_exit:
120 119 # don't prompt, just terminate the kernel if we own it
121 120 # or leave it alone if we don't
122 121 keepkernel = not self._existing
123 122
124 123 if keepkernel is None: #show prompt
125 124 if kernel_manager and kernel_manager.channels_running:
126 125 title = self.window().windowTitle()
127 126 cancel = QtGui.QMessageBox.Cancel
128 127 okay = QtGui.QMessageBox.Ok
129 128 if self._may_close:
130 129 msg = "You are closing this Console window."
131 130 info = "Would you like to quit the Kernel and all attached Consoles as well?"
132 131 justthis = QtGui.QPushButton("&No, just this Console", self)
133 132 justthis.setShortcut('N')
134 133 closeall = QtGui.QPushButton("&Yes, quit everything", self)
135 134 closeall.setShortcut('Y')
136 135 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
137 136 title, msg)
138 137 box.setInformativeText(info)
139 138 box.addButton(cancel)
140 139 box.addButton(justthis, QtGui.QMessageBox.NoRole)
141 140 box.addButton(closeall, QtGui.QMessageBox.YesRole)
142 141 box.setDefaultButton(closeall)
143 142 box.setEscapeButton(cancel)
144 143 reply = box.exec_()
145 144 if reply == 1: # close All
146 145 kernel_manager.shutdown_kernel()
147 146 #kernel_manager.stop_channels()
148 147 event.accept()
149 148 elif reply == 0: # close Console
150 149 if not self._existing:
151 150 # Have kernel: don't quit, just close the window
152 151 self._app.setQuitOnLastWindowClosed(False)
153 152 self.deleteLater()
154 153 event.accept()
155 154 else:
156 155 event.ignore()
157 156 else:
158 157 reply = QtGui.QMessageBox.question(self, title,
159 158 "Are you sure you want to close this Console?"+
160 159 "\nThe Kernel and other Consoles will remain active.",
161 160 okay|cancel,
162 161 defaultButton=okay
163 162 )
164 163 if reply == okay:
165 164 event.accept()
166 165 else:
167 166 event.ignore()
168 167 elif keepkernel: #close console but leave kernel running (no prompt)
169 168 if kernel_manager and kernel_manager.channels_running:
170 169 if not self._existing:
171 170 # I have the kernel: don't quit, just close the window
172 171 self._app.setQuitOnLastWindowClosed(False)
173 172 event.accept()
174 173 else: #close console and kernel (no prompt)
175 174 if kernel_manager and kernel_manager.channels_running:
176 175 kernel_manager.shutdown_kernel()
177 176 event.accept()
178 177
179 178 #-----------------------------------------------------------------------------
180 179 # Aliases and Flags
181 180 #-----------------------------------------------------------------------------
182 181
183 182 flags = dict(ipkernel_flags)
184 183 qt_flags = {
185 184 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
186 185 "Connect to an existing kernel. If no argument specified, guess most recent"),
187 186 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
188 187 "Use a pure Python kernel instead of an IPython kernel."),
189 188 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
190 189 "Disable rich text support."),
191 190 }
192 191 qt_flags.update(boolean_flag(
193 192 'gui-completion', 'ConsoleWidget.gui_completion',
194 193 "use a GUI widget for tab completion",
195 194 "use plaintext output for completion"
196 195 ))
197 196 qt_flags.update(boolean_flag(
198 197 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
199 198 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
200 199 to force a direct exit without any confirmation.
201 200 """,
202 201 """Don't prompt the user when exiting. This will terminate the kernel
203 202 if it is owned by the frontend, and leave it alive if it is external.
204 203 """
205 204 ))
206 205 flags.update(qt_flags)
207 206
208 207 aliases = dict(ipkernel_aliases)
209 208
210 209 qt_aliases = dict(
211 210 hb = 'IPythonQtConsoleApp.hb_port',
212 211 shell = 'IPythonQtConsoleApp.shell_port',
213 212 iopub = 'IPythonQtConsoleApp.iopub_port',
214 213 stdin = 'IPythonQtConsoleApp.stdin_port',
215 214 ip = 'IPythonQtConsoleApp.ip',
216 215 existing = 'IPythonQtConsoleApp.existing',
217 216 f = 'IPythonQtConsoleApp.connection_file',
218 217
219 218 style = 'IPythonWidget.syntax_style',
220 219 stylesheet = 'IPythonQtConsoleApp.stylesheet',
221 220 colors = 'ZMQInteractiveShell.colors',
222 221
223 222 editor = 'IPythonWidget.editor',
224 223 paging = 'ConsoleWidget.paging',
225 224 ssh = 'IPythonQtConsoleApp.sshserver',
226 225 )
227 226 aliases.update(qt_aliases)
228 227
229 228
230 229 #-----------------------------------------------------------------------------
231 230 # IPythonQtConsole
232 231 #-----------------------------------------------------------------------------
233 232
234 233
235 234 class IPythonQtConsoleApp(BaseIPythonApplication):
236 235 name = 'ipython-qtconsole'
237 236 default_config_file_name='ipython_config.py'
238 237
239 238 description = """
240 239 The IPython QtConsole.
241 240
242 241 This launches a Console-style application using Qt. It is not a full
243 242 console, in that launched terminal subprocesses will not be able to accept
244 243 input.
245 244
246 245 The QtConsole supports various extra features beyond the Terminal IPython
247 246 shell, such as inline plotting with matplotlib, via:
248 247
249 248 ipython qtconsole --pylab=inline
250 249
251 250 as well as saving your session as HTML, and printing the output.
252 251
253 252 """
254 253 examples = _examples
255 254
256 255 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
257 256 flags = Dict(flags)
258 257 aliases = Dict(aliases)
259 258
260 259 kernel_argv = List(Unicode)
261 260
262 261 # create requested profiles by default, if they don't exist:
263 262 auto_create = CBool(True)
264 263 # connection info:
265 264 ip = Unicode(LOCALHOST, config=True,
266 265 help="""Set the kernel\'s IP address [default localhost].
267 266 If the IP address is something other than localhost, then
268 267 Consoles on other machines will be able to connect
269 268 to the Kernel, so be careful!"""
270 269 )
271 270
272 271 sshserver = Unicode('', config=True,
273 272 help="""The SSH server to use to connect to the kernel.""")
274 273 sshkey = Unicode('', config=True,
275 274 help="""Path to the ssh key to use for logging in to the ssh server.""")
276 275
277 276 hb_port = Int(0, config=True,
278 277 help="set the heartbeat port [default: random]")
279 278 shell_port = Int(0, config=True,
280 279 help="set the shell (XREP) port [default: random]")
281 280 iopub_port = Int(0, config=True,
282 281 help="set the iopub (PUB) port [default: random]")
283 282 stdin_port = Int(0, config=True,
284 283 help="set the stdin (XREQ) port [default: random]")
285 284 connection_file = Unicode('', config=True,
286 285 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
287 286
288 287 This file will contain the IP, ports, and authentication key needed to connect
289 288 clients to this kernel. By default, this file will be created in the security-dir
290 289 of the current profile, but can be specified by absolute path.
291 290 """)
292 291 def _connection_file_default(self):
293 292 return 'kernel-%i.json' % os.getpid()
294 293
295 294 existing = Unicode('', config=True,
296 295 help="""Connect to an already running kernel""")
297 296
298 297 stylesheet = Unicode('', config=True,
299 298 help="path to a custom CSS stylesheet")
300 299
301 300 pure = CBool(False, config=True,
302 301 help="Use a pure Python kernel instead of an IPython kernel.")
303 302 plain = CBool(False, config=True,
304 303 help="Use a plaintext widget instead of rich text (plain can't print/save).")
305 304
306 305 def _pure_changed(self, name, old, new):
307 306 kind = 'plain' if self.plain else 'rich'
308 307 self.config.ConsoleWidget.kind = kind
309 308 if self.pure:
310 309 self.widget_factory = FrontendWidget
311 310 elif self.plain:
312 311 self.widget_factory = IPythonWidget
313 312 else:
314 313 self.widget_factory = RichIPythonWidget
315 314
316 315 _plain_changed = _pure_changed
317 316
318 317 confirm_exit = CBool(True, config=True,
319 318 help="""
320 319 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
321 320 to force a direct exit without any confirmation.""",
322 321 )
323 322
324 323 # the factory for creating a widget
325 324 widget_factory = Any(RichIPythonWidget)
326 325
327 326 def parse_command_line(self, argv=None):
328 327 super(IPythonQtConsoleApp, self).parse_command_line(argv)
329 328 if argv is None:
330 329 argv = sys.argv[1:]
331 330
332 331 self.kernel_argv = list(argv) # copy
333 332 # kernel should inherit default config file from frontend
334 333 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
335 334 # Scrub frontend-specific flags
336 335 for a in argv:
337 336 if a.startswith('-') and a.lstrip('-') in qt_flags:
338 337 self.kernel_argv.remove(a)
339 338 swallow_next = False
340 339 for a in argv:
341 340 if swallow_next:
342 341 self.kernel_argv.remove(a)
343 342 swallow_next = False
344 343 continue
345 344 if a.startswith('-'):
346 345 split = a.lstrip('-').split('=')[0]
347 346 alias = split[0]
348 347 if alias in qt_aliases:
349 348 self.kernel_argv.remove(a)
350 349 if len(split) == 1:
351 350 # alias passed with arg via space
352 351 swallow_next = True
353 352
354 353 def init_connection_file(self):
355 354 """find the connection file, and load the info if found.
356 355
357 356 The current working directory and the current profile's security
358 357 directory will be searched for the file if it is not given by
359 358 absolute path.
360 359
361 360 When attempting to connect to an existing kernel and the `--existing`
362 361 argument does not match an existing file, it will be interpreted as a
363 362 fileglob, and the matching file in the current profile's security dir
364 363 with the latest access time will be used.
365 364 """
366 sec = self.profile_dir.security_dir
367 365 if self.existing:
368 366 try:
369 # first, try explicit name
370 cf = filefind(self.existing, ['.', sec])
371 except IOError:
372 # not found by full name
373 if '*' in self.existing:
374 # given as a glob already
375 pat = self.existing
376 else:
377 # accept any substring match
378 pat = '*%s*' % self.existing
379 matches = glob.glob( os.path.join(sec, pat) )
380 if not matches:
381 self.log.critical("Could not find existing kernel connection file %s", self.existing)
382 self.exit(1)
383 else:
384 # get most recent match:
385 cf = sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
367 cf = find_connection_file(self.existing)
368 except Exception:
369 self.log.critical("Could not find existing kernel connection file %s", self.existing)
370 self.exit(1)
386 371 self.log.info("Connecting to existing kernel: %s" % cf)
387 372 self.connection_file = cf
388 373 # should load_connection_file only be used for existing?
389 374 # as it is now, this allows reusing ports if an existing
390 375 # file is requested
391 376 self.load_connection_file()
392 377
393 378 def load_connection_file(self):
394 379 """load ip/port/hmac config from JSON connection file"""
395 380 # this is identical to KernelApp.load_connection_file
396 381 # perhaps it can be centralized somewhere?
397 382 try:
398 383 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
399 384 except IOError:
400 385 self.log.debug("Connection File not found: %s", self.connection_file)
401 386 return
402 387 self.log.debug(u"Loading connection file %s", fname)
403 388 with open(fname) as f:
404 389 s = f.read()
405 390 cfg = json.loads(s)
406 391 if self.ip == LOCALHOST and 'ip' in cfg:
407 392 # not overridden by config or cl_args
408 393 self.ip = cfg['ip']
409 394 for channel in ('hb', 'shell', 'iopub', 'stdin'):
410 395 name = channel + '_port'
411 396 if getattr(self, name) == 0 and name in cfg:
412 397 # not overridden by config or cl_args
413 398 setattr(self, name, cfg[name])
414 399 if 'key' in cfg:
415 400 self.config.Session.key = str_to_bytes(cfg['key'])
416 401
417 402 def init_ssh(self):
418 403 """set up ssh tunnels, if needed."""
419 404 if not self.sshserver and not self.sshkey:
420 405 return
421 406
422 407 if self.sshkey and not self.sshserver:
423 408 # specifying just the key implies that we are connecting directly
424 409 self.sshserver = self.ip
425 410 self.ip = LOCALHOST
426 411
427 412 # build connection dict for tunnels:
428 413 info = dict(ip=self.ip,
429 414 shell_port=self.shell_port,
430 415 iopub_port=self.iopub_port,
431 416 stdin_port=self.stdin_port,
432 417 hb_port=self.hb_port
433 418 )
434 419
435 420 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
436 421
437 422 # tunnels return a new set of ports, which will be on localhost:
438 423 self.ip = LOCALHOST
439 424 try:
440 425 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
441 426 except:
442 427 # even catch KeyboardInterrupt
443 428 self.log.error("Could not setup tunnels", exc_info=True)
444 429 self.exit(1)
445 430
446 431 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
447 432
448 433 cf = self.connection_file
449 434 base,ext = os.path.splitext(cf)
450 435 base = os.path.basename(base)
451 436 self.connection_file = os.path.basename(base)+'-ssh'+ext
452 437 self.log.critical("To connect another client via this tunnel, use:")
453 438 self.log.critical("--existing %s" % self.connection_file)
454 439
455 440 def init_kernel_manager(self):
456 441 # Don't let Qt or ZMQ swallow KeyboardInterupts.
457 442 signal.signal(signal.SIGINT, signal.SIG_DFL)
458 443 sec = self.profile_dir.security_dir
459 444 try:
460 445 cf = filefind(self.connection_file, ['.', sec])
461 446 except IOError:
462 447 # file might not exist
463 448 if self.connection_file == os.path.basename(self.connection_file):
464 449 # just shortname, put it in security dir
465 450 cf = os.path.join(sec, self.connection_file)
466 451 else:
467 452 cf = self.connection_file
468 453
469 454 # Create a KernelManager and start a kernel.
470 455 self.kernel_manager = QtKernelManager(
471 456 ip=self.ip,
472 457 shell_port=self.shell_port,
473 458 iopub_port=self.iopub_port,
474 459 stdin_port=self.stdin_port,
475 460 hb_port=self.hb_port,
476 461 connection_file=cf,
477 462 config=self.config,
478 463 )
479 464 # start the kernel
480 465 if not self.existing:
481 466 kwargs = dict(ipython=not self.pure)
482 467 kwargs['extra_arguments'] = self.kernel_argv
483 468 self.kernel_manager.start_kernel(**kwargs)
484 469 elif self.sshserver:
485 470 # ssh, write new connection file
486 471 self.kernel_manager.write_connection_file()
487 472 self.kernel_manager.start_channels()
488 473
489 474
490 475 def init_qt_elements(self):
491 476 # Create the widget.
492 477 self.app = QtGui.QApplication([])
493 478 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
494 479 self.widget = self.widget_factory(config=self.config,
495 480 local_kernel=local_kernel)
496 481 self.widget.kernel_manager = self.kernel_manager
497 482 self.window = MainWindow(self.app, self.widget, self.existing,
498 483 may_close=local_kernel,
499 484 confirm_exit=self.confirm_exit)
500 485 self.window.setWindowTitle('Python' if self.pure else 'IPython')
501 486
502 487 def init_colors(self):
503 488 """Configure the coloring of the widget"""
504 489 # Note: This will be dramatically simplified when colors
505 490 # are removed from the backend.
506 491
507 492 if self.pure:
508 493 # only IPythonWidget supports styling
509 494 return
510 495
511 496 # parse the colors arg down to current known labels
512 497 try:
513 498 colors = self.config.ZMQInteractiveShell.colors
514 499 except AttributeError:
515 500 colors = None
516 501 try:
517 502 style = self.config.IPythonWidget.colors
518 503 except AttributeError:
519 504 style = None
520 505
521 506 # find the value for colors:
522 507 if colors:
523 508 colors=colors.lower()
524 509 if colors in ('lightbg', 'light'):
525 510 colors='lightbg'
526 511 elif colors in ('dark', 'linux'):
527 512 colors='linux'
528 513 else:
529 514 colors='nocolor'
530 515 elif style:
531 516 if style=='bw':
532 517 colors='nocolor'
533 518 elif styles.dark_style(style):
534 519 colors='linux'
535 520 else:
536 521 colors='lightbg'
537 522 else:
538 523 colors=None
539 524
540 525 # Configure the style.
541 526 widget = self.widget
542 527 if style:
543 528 widget.style_sheet = styles.sheet_from_template(style, colors)
544 529 widget.syntax_style = style
545 530 widget._syntax_style_changed()
546 531 widget._style_sheet_changed()
547 532 elif colors:
548 533 # use a default style
549 534 widget.set_default_style(colors=colors)
550 535 else:
551 536 # this is redundant for now, but allows the widget's
552 537 # defaults to change
553 538 widget.set_default_style()
554 539
555 540 if self.stylesheet:
556 541 # we got an expicit stylesheet
557 542 if os.path.isfile(self.stylesheet):
558 543 with open(self.stylesheet) as f:
559 544 sheet = f.read()
560 545 widget.style_sheet = sheet
561 546 widget._style_sheet_changed()
562 547 else:
563 548 raise IOError("Stylesheet %r not found."%self.stylesheet)
564 549
565 550 def initialize(self, argv=None):
566 551 super(IPythonQtConsoleApp, self).initialize(argv)
567 552 self.init_connection_file()
568 553 default_secure(self.config)
569 554 self.init_ssh()
570 555 self.init_kernel_manager()
571 556 self.init_qt_elements()
572 557 self.init_colors()
573 558 self.init_window_shortcut()
574 559
575 560 def init_window_shortcut(self):
576 561 fullScreenAction = QtGui.QAction('Toggle Full Screen', self.window)
577 562 fullScreenAction.setShortcut('Ctrl+Meta+Space')
578 563 fullScreenAction.triggered.connect(self.toggleFullScreen)
579 564 self.window.addAction(fullScreenAction)
580 565
581 566 def toggleFullScreen(self):
582 567 if not self.window.isFullScreen():
583 568 self.window.showFullScreen()
584 569 else:
585 570 self.window.showNormal()
586 571
587 572 def start(self):
588 573
589 574 # draw the window
590 575 self.window.show()
591 576
592 577 # Start the application main loop.
593 578 self.app.exec_()
594 579
595 580 #-----------------------------------------------------------------------------
596 581 # Main entry point
597 582 #-----------------------------------------------------------------------------
598 583
599 584 def main():
600 585 app = IPythonQtConsoleApp()
601 586 app.initialize()
602 587 app.start()
603 588
604 589
605 590 if __name__ == '__main__':
606 591 main()
@@ -1,152 +1,255 b''
1 1 """Utilities for connecting to kernels
2 2
3 3 Authors:
4 4
5 5 * Min Ragan-Kelley
6 6
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2011 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 import glob
20 21 import json
22 import os
21 23 import sys
22 24 from getpass import getpass
23 25 from subprocess import Popen, PIPE
24 26
25 27 # external imports
26 28 from IPython.external.ssh import tunnel
27 29
28 30 # IPython imports
29 from IPython.utils.path import filefind
31 from IPython.core.profiledir import ProfileDir
32 from IPython.utils.path import filefind, get_ipython_dir
30 33 from IPython.utils.py3compat import str_to_bytes
31 34
32 35
33 36 #-----------------------------------------------------------------------------
34 37 # Functions
35 38 #-----------------------------------------------------------------------------
36 39
37 40 def get_connection_file(app=None):
38 41 """Return the path to the connection file of an app
39 42
40 43 Parameters
41 44 ----------
42 45 app : KernelApp instance [optional]
43 46 If unspecified, the currently running app will be used
44 47 """
45 48 if app is None:
46 49 from IPython.zmq.kernelapp import KernelApp
47 50 if not KernelApp.initialized():
48 51 raise RuntimeError("app not specified, and not in a running Kernel")
49 52
50 53 app = KernelApp.instance()
51 54 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
52
53 def get_connection_info(unpack=False):
55
56 def find_connection_file(filename, profile=None):
57 """find a connection file, and return its absolute path.
58
59 The current working directory and the profile's security
60 directory will be searched for the file if it is not given by
61 absolute path.
62
63 If profile is unspecified, then the current running application's
64 profile will be used, or 'default', if not run from IPython.
65
66 If the argument does not match an existing file, it will be interpreted as a
67 fileglob, and the matching file in the profile's security dir with
68 the latest access time will be used.
69
70 Parameters
71 ----------
72 filename : str
73 The connection file or fileglob to search for.
74 profile : str [optional]
75 The name of the profile to use when searching for the connection file,
76 if different from the current IPython session or 'default'.
77
78 Returns
79 -------
80 str : The absolute path of the connection file.
81 """
82 from IPython.core.application import BaseIPythonApplication as IPApp
83 try:
84 # quick check for absolute path, before going through logic
85 return filefind(filename)
86 except IOError:
87 pass
88
89 if profile is None:
90 # profile unspecified, check if running from an IPython app
91 if IPApp.initialized():
92 app = IPApp.instance()
93 profile_dir = app.profile_dir
94 else:
95 # not running in IPython, use default profile
96 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
97 else:
98 # find profiledir by profile name:
99 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
100 security_dir = profile_dir.security_dir
101
102 try:
103 # first, try explicit name
104 return filefind(filename, ['.', security_dir])
105 except IOError:
106 pass
107
108 # not found by full name
109
110 if '*' in filename:
111 # given as a glob already
112 pat = filename
113 else:
114 # accept any substring match
115 pat = '*%s*' % filename
116 matches = glob.glob( os.path.join(security_dir, pat) )
117 if not matches:
118 raise IOError("Could not find %r in %r" % (filename, security_dir))
119 elif len(matches) == 1:
120 return matches[0]
121 else:
122 # get most recent match, by access time:
123 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
124
125 def get_connection_info(connection_file=None, unpack=False, profile=None):
54 126 """Return the connection information for the current Kernel.
55 127
56 128 Parameters
57 129 ----------
130 connection_file : str [optional]
131 The connection file to be used. Can be given by absolute path, or
132 IPython will search in the security directory of a given profile.
133 If run from IPython,
134
135 If unspecified, the connection file for the currently running
136 IPython Kernel will be used, which is only allowed from inside a kernel.
58 137 unpack : bool [default: False]
59 138 if True, return the unpacked dict, otherwise just the string contents
60 139 of the file.
140 profile : str [optional]
141 The name of the profile to use when searching for the connection file,
142 if different from the current IPython session or 'default'.
143
61 144
62 145 Returns
63 146 -------
64 147 The connection dictionary of the current kernel, as string or dict,
65 148 depending on `unpack`.
66 149 """
67 cf = get_connection_file()
150 if connection_file is None:
151 # get connection file from current kernel
152 cf = get_connection_file()
153 else:
154 # connection file specified, allow shortnames:
155 cf = find_connection_file(connection_file, profile=profile)
156
68 157 with open(cf) as f:
69 158 info = f.read()
70 159
71 160 if unpack:
72 161 info = json.loads(info)
73 162 # ensure key is bytes:
74 163 info['key'] = str_to_bytes(info.get('key', ''))
75 164 return info
76 165
77 def connect_qtconsole(argv=None):
166 def connect_qtconsole(connection_file=None, argv=None, profile=None):
78 167 """Connect a qtconsole to the current kernel.
79 168
80 169 This is useful for connecting a second qtconsole to a kernel, or to a
81 170 local notebook.
82 171
83 172 Parameters
84 173 ----------
174 connection_file : str [optional]
175 The connection file to be used. Can be given by absolute path, or
176 IPython will search in the security directory of a given profile.
177 If run from IPython,
178
179 If unspecified, the connection file for the currently running
180 IPython Kernel will be used, which is only allowed from inside a kernel.
85 181 argv : list [optional]
86 182 Any extra args to be passed to the console.
183 profile : str [optional]
184 The name of the profile to use when searching for the connection file,
185 if different from the current IPython session or 'default'.
186
87 187
88 188 Returns
89 189 -------
90 190 subprocess.Popen instance running the qtconsole frontend
91 191 """
92 192 argv = [] if argv is None else argv
93 193
94 # get connection file from current kernel
95 cf = get_connection_file()
194 if connection_file is None:
195 # get connection file from current kernel
196 cf = get_connection_file()
197 else:
198 cf = find_connection_file(connection_file, profile=profile)
96 199
97 200 cmd = ';'.join([
98 201 "from IPython.frontend.qt.console import qtconsoleapp",
99 202 "qtconsoleapp.main()"
100 203 ])
101 204
102 205 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
103 206
104 207 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
105 208 """tunnel connections to a kernel via ssh
106 209
107 210 This will open four SSH tunnels from localhost on this machine to the
108 211 ports associated with the kernel. They can be either direct
109 212 localhost-localhost tunnels, or if an intermediate server is necessary,
110 213 the kernel must be listening on a public IP.
111 214
112 215 Parameters
113 216 ----------
114 217 connection_info : dict or str (path)
115 218 Either a connection dict, or the path to a JSON connection file
116 219 sshserver : str
117 220 The ssh sever to use to tunnel to the kernel. Can be a full
118 221 `user@server:port` string. ssh config aliases are respected.
119 222 sshkey : str [optional]
120 223 Path to file containing ssh key to use for authentication.
121 224 Only necessary if your ssh config does not already associate
122 225 a keyfile with the host.
123 226
124 227 Returns
125 228 -------
126 229
127 230 (shell, iopub, stdin, hb) : ints
128 231 The four ports on localhost that have been forwarded to the kernel.
129 232 """
130 233 if isinstance(connection_info, basestring):
131 234 # it's a path, unpack it
132 235 with open(connection_info) as f:
133 236 connection_info = json.loads(f.read())
134 237
135 238 cf = connection_info
136 239
137 240 lports = tunnel.select_random_ports(4)
138 241 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
139 242
140 243 remote_ip = cf['ip']
141 244
142 245 if tunnel.try_passwordless_ssh(sshserver, sshkey):
143 246 password=False
144 247 else:
145 248 password = getpass("SSH Password for %s: "%sshserver)
146 249
147 250 for lp,rp in zip(lports, rports):
148 251 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
149 252
150 253 return tuple(lports)
151 254
152 255
@@ -1,489 +1,489 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 sys
22 22 from subprocess import Popen, PIPE
23 23
24 24 # Our own
25 25 from IPython.core.interactiveshell import (
26 26 InteractiveShell, InteractiveShellABC
27 27 )
28 28 from IPython.core import page
29 29 from IPython.core.autocall import ZMQExitAutocall
30 30 from IPython.core.displaypub import DisplayPublisher
31 31 from IPython.core.macro import Macro
32 32 from IPython.core.magic import MacroToEdit
33 33 from IPython.core.payloadpage import install_payload_page
34 34 from IPython.lib.kernel import (
35 35 get_connection_file, get_connection_info, connect_qtconsole
36 36 )
37 37 from IPython.utils import io
38 38 from IPython.utils.jsonutil import json_clean
39 39 from IPython.utils.path import get_py_filename
40 40 from IPython.utils.process import arg_split
41 41 from IPython.utils.traitlets import Instance, Type, Dict, CBool
42 42 from IPython.utils.warn import warn, error
43 43 from IPython.zmq.displayhook import ZMQShellDisplayHook, _encode_binary
44 44 from IPython.zmq.session import extract_header
45 45 from session import Session
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Globals and side-effects
49 49 #-----------------------------------------------------------------------------
50 50
51 51 # Install the payload version of page.
52 52 install_payload_page()
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Functions and classes
56 56 #-----------------------------------------------------------------------------
57 57
58 58 class ZMQDisplayPublisher(DisplayPublisher):
59 59 """A display publisher that publishes data using a ZeroMQ PUB socket."""
60 60
61 61 session = Instance(Session)
62 62 pub_socket = Instance('zmq.Socket')
63 63 parent_header = Dict({})
64 64
65 65 def set_parent(self, parent):
66 66 """Set the parent for outbound messages."""
67 67 self.parent_header = extract_header(parent)
68 68
69 69 def publish(self, source, data, metadata=None):
70 70 if metadata is None:
71 71 metadata = {}
72 72 self._validate_data(source, data, metadata)
73 73 content = {}
74 74 content['source'] = source
75 75 _encode_binary(data)
76 76 content['data'] = data
77 77 content['metadata'] = metadata
78 78 self.session.send(
79 79 self.pub_socket, u'display_data', json_clean(content),
80 80 parent=self.parent_header
81 81 )
82 82
83 83
84 84 class ZMQInteractiveShell(InteractiveShell):
85 85 """A subclass of InteractiveShell for ZMQ."""
86 86
87 87 displayhook_class = Type(ZMQShellDisplayHook)
88 88 display_pub_class = Type(ZMQDisplayPublisher)
89 89
90 90 # Override the traitlet in the parent class, because there's no point using
91 91 # readline for the kernel. Can be removed when the readline code is moved
92 92 # to the terminal frontend.
93 93 colors_force = CBool(True)
94 94 readline_use = CBool(False)
95 95 # autoindent has no meaning in a zmqshell, and attempting to enable it
96 96 # will print a warning in the absence of readline.
97 97 autoindent = CBool(False)
98 98
99 99 exiter = Instance(ZMQExitAutocall)
100 100 def _exiter_default(self):
101 101 return ZMQExitAutocall(self)
102 102
103 103 keepkernel_on_exit = None
104 104
105 105 def init_environment(self):
106 106 """Configure the user's environment.
107 107
108 108 """
109 109 env = os.environ
110 110 # These two ensure 'ls' produces nice coloring on BSD-derived systems
111 111 env['TERM'] = 'xterm-color'
112 112 env['CLICOLOR'] = '1'
113 113 # Since normal pagers don't work at all (over pexpect we don't have
114 114 # single-key control of the subprocess), try to disable paging in
115 115 # subprocesses as much as possible.
116 116 env['PAGER'] = 'cat'
117 117 env['GIT_PAGER'] = 'cat'
118 118
119 119 def auto_rewrite_input(self, cmd):
120 120 """Called to show the auto-rewritten input for autocall and friends.
121 121
122 122 FIXME: this payload is currently not correctly processed by the
123 123 frontend.
124 124 """
125 125 new = self.displayhook.prompt1.auto_rewrite() + cmd
126 126 payload = dict(
127 127 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
128 128 transformed_input=new,
129 129 )
130 130 self.payload_manager.write_payload(payload)
131 131
132 132 def ask_exit(self):
133 133 """Engage the exit actions."""
134 134 payload = dict(
135 135 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
136 136 exit=True,
137 137 keepkernel=self.keepkernel_on_exit,
138 138 )
139 139 self.payload_manager.write_payload(payload)
140 140
141 141 def _showtraceback(self, etype, evalue, stb):
142 142
143 143 exc_content = {
144 144 u'traceback' : stb,
145 145 u'ename' : unicode(etype.__name__),
146 146 u'evalue' : unicode(evalue)
147 147 }
148 148
149 149 dh = self.displayhook
150 150 # Send exception info over pub socket for other clients than the caller
151 151 # to pick up
152 152 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header)
153 153
154 154 # FIXME - Hack: store exception info in shell object. Right now, the
155 155 # caller is reading this info after the fact, we need to fix this logic
156 156 # to remove this hack. Even uglier, we need to store the error status
157 157 # here, because in the main loop, the logic that sets it is being
158 158 # skipped because runlines swallows the exceptions.
159 159 exc_content[u'status'] = u'error'
160 160 self._reply_content = exc_content
161 161 # /FIXME
162 162
163 163 return exc_content
164 164
165 165 #------------------------------------------------------------------------
166 166 # Magic overrides
167 167 #------------------------------------------------------------------------
168 168 # Once the base class stops inheriting from magic, this code needs to be
169 169 # moved into a separate machinery as well. For now, at least isolate here
170 170 # the magics which this class needs to implement differently from the base
171 171 # class, or that are unique to it.
172 172
173 173 def magic_doctest_mode(self,parameter_s=''):
174 174 """Toggle doctest mode on and off.
175 175
176 176 This mode is intended to make IPython behave as much as possible like a
177 177 plain Python shell, from the perspective of how its prompts, exceptions
178 178 and output look. This makes it easy to copy and paste parts of a
179 179 session into doctests. It does so by:
180 180
181 181 - Changing the prompts to the classic ``>>>`` ones.
182 182 - Changing the exception reporting mode to 'Plain'.
183 183 - Disabling pretty-printing of output.
184 184
185 185 Note that IPython also supports the pasting of code snippets that have
186 186 leading '>>>' and '...' prompts in them. This means that you can paste
187 187 doctests from files or docstrings (even if they have leading
188 188 whitespace), and the code will execute correctly. You can then use
189 189 '%history -t' to see the translated history; this will give you the
190 190 input after removal of all the leading prompts and whitespace, which
191 191 can be pasted back into an editor.
192 192
193 193 With these features, you can switch into this mode easily whenever you
194 194 need to do testing and changes to doctests, without having to leave
195 195 your existing IPython session.
196 196 """
197 197
198 198 from IPython.utils.ipstruct import Struct
199 199
200 200 # Shorthands
201 201 shell = self.shell
202 202 disp_formatter = self.shell.display_formatter
203 203 ptformatter = disp_formatter.formatters['text/plain']
204 204 # dstore is a data store kept in the instance metadata bag to track any
205 205 # changes we make, so we can undo them later.
206 206 dstore = shell.meta.setdefault('doctest_mode', Struct())
207 207 save_dstore = dstore.setdefault
208 208
209 209 # save a few values we'll need to recover later
210 210 mode = save_dstore('mode', False)
211 211 save_dstore('rc_pprint', ptformatter.pprint)
212 212 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
213 213 save_dstore('xmode', shell.InteractiveTB.mode)
214 214
215 215 if mode == False:
216 216 # turn on
217 217 ptformatter.pprint = False
218 218 disp_formatter.plain_text_only = True
219 219 shell.magic_xmode('Plain')
220 220 else:
221 221 # turn off
222 222 ptformatter.pprint = dstore.rc_pprint
223 223 disp_formatter.plain_text_only = dstore.rc_plain_text_only
224 224 shell.magic_xmode(dstore.xmode)
225 225
226 226 # Store new mode and inform on console
227 227 dstore.mode = bool(1-int(mode))
228 228 mode_label = ['OFF','ON'][dstore.mode]
229 229 print('Doctest mode is:', mode_label)
230 230
231 231 # Send the payload back so that clients can modify their prompt display
232 232 payload = dict(
233 233 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode',
234 234 mode=dstore.mode)
235 235 self.payload_manager.write_payload(payload)
236 236
237 237 def magic_edit(self,parameter_s='',last_call=['','']):
238 238 """Bring up an editor and execute the resulting code.
239 239
240 240 Usage:
241 241 %edit [options] [args]
242 242
243 243 %edit runs an external text editor. You will need to set the command for
244 244 this editor via the ``TerminalInteractiveShell.editor`` option in your
245 245 configuration file before it will work.
246 246
247 247 This command allows you to conveniently edit multi-line code right in
248 248 your IPython session.
249 249
250 250 If called without arguments, %edit opens up an empty editor with a
251 251 temporary file and will execute the contents of this file when you
252 252 close it (don't forget to save it!).
253 253
254 254
255 255 Options:
256 256
257 257 -n <number>: open the editor at a specified line number. By default,
258 258 the IPython editor hook uses the unix syntax 'editor +N filename', but
259 259 you can configure this by providing your own modified hook if your
260 260 favorite editor supports line-number specifications with a different
261 261 syntax.
262 262
263 263 -p: this will call the editor with the same data as the previous time
264 264 it was used, regardless of how long ago (in your current session) it
265 265 was.
266 266
267 267 -r: use 'raw' input. This option only applies to input taken from the
268 268 user's history. By default, the 'processed' history is used, so that
269 269 magics are loaded in their transformed version to valid Python. If
270 270 this option is given, the raw input as typed as the command line is
271 271 used instead. When you exit the editor, it will be executed by
272 272 IPython's own processor.
273 273
274 274 -x: do not execute the edited code immediately upon exit. This is
275 275 mainly useful if you are editing programs which need to be called with
276 276 command line arguments, which you can then do using %run.
277 277
278 278
279 279 Arguments:
280 280
281 281 If arguments are given, the following possibilites exist:
282 282
283 283 - The arguments are numbers or pairs of colon-separated numbers (like
284 284 1 4:8 9). These are interpreted as lines of previous input to be
285 285 loaded into the editor. The syntax is the same of the %macro command.
286 286
287 287 - If the argument doesn't start with a number, it is evaluated as a
288 288 variable and its contents loaded into the editor. You can thus edit
289 289 any string which contains python code (including the result of
290 290 previous edits).
291 291
292 292 - If the argument is the name of an object (other than a string),
293 293 IPython will try to locate the file where it was defined and open the
294 294 editor at the point where it is defined. You can use `%edit function`
295 295 to load an editor exactly at the point where 'function' is defined,
296 296 edit it and have the file be executed automatically.
297 297
298 298 If the object is a macro (see %macro for details), this opens up your
299 299 specified editor with a temporary file containing the macro's data.
300 300 Upon exit, the macro is reloaded with the contents of the file.
301 301
302 302 Note: opening at an exact line is only supported under Unix, and some
303 303 editors (like kedit and gedit up to Gnome 2.8) do not understand the
304 304 '+NUMBER' parameter necessary for this feature. Good editors like
305 305 (X)Emacs, vi, jed, pico and joe all do.
306 306
307 307 - If the argument is not found as a variable, IPython will look for a
308 308 file with that name (adding .py if necessary) and load it into the
309 309 editor. It will execute its contents with execfile() when you exit,
310 310 loading any code in the file into your interactive namespace.
311 311
312 312 After executing your code, %edit will return as output the code you
313 313 typed in the editor (except when it was an existing file). This way
314 314 you can reload the code in further invocations of %edit as a variable,
315 315 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
316 316 the output.
317 317
318 318 Note that %edit is also available through the alias %ed.
319 319
320 320 This is an example of creating a simple function inside the editor and
321 321 then modifying it. First, start up the editor:
322 322
323 323 In [1]: ed
324 324 Editing... done. Executing edited code...
325 325 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
326 326
327 327 We can then call the function foo():
328 328
329 329 In [2]: foo()
330 330 foo() was defined in an editing session
331 331
332 332 Now we edit foo. IPython automatically loads the editor with the
333 333 (temporary) file where foo() was previously defined:
334 334
335 335 In [3]: ed foo
336 336 Editing... done. Executing edited code...
337 337
338 338 And if we call foo() again we get the modified version:
339 339
340 340 In [4]: foo()
341 341 foo() has now been changed!
342 342
343 343 Here is an example of how to edit a code snippet successive
344 344 times. First we call the editor:
345 345
346 346 In [5]: ed
347 347 Editing... done. Executing edited code...
348 348 hello
349 349 Out[5]: "print 'hello'n"
350 350
351 351 Now we call it again with the previous output (stored in _):
352 352
353 353 In [6]: ed _
354 354 Editing... done. Executing edited code...
355 355 hello world
356 356 Out[6]: "print 'hello world'n"
357 357
358 358 Now we call it with the output #8 (stored in _8, also as Out[8]):
359 359
360 360 In [7]: ed _8
361 361 Editing... done. Executing edited code...
362 362 hello again
363 363 Out[7]: "print 'hello again'n"
364 364 """
365 365
366 366 opts,args = self.parse_options(parameter_s,'prn:')
367 367
368 368 try:
369 369 filename, lineno, _ = self._find_edit_target(args, opts, last_call)
370 370 except MacroToEdit as e:
371 371 # TODO: Implement macro editing over 2 processes.
372 372 print("Macro editing not yet implemented in 2-process model.")
373 373 return
374 374
375 375 # Make sure we send to the client an absolute path, in case the working
376 376 # directory of client and kernel don't match
377 377 filename = os.path.abspath(filename)
378 378
379 379 payload = {
380 380 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
381 381 'filename' : filename,
382 382 'line_number' : lineno
383 383 }
384 384 self.payload_manager.write_payload(payload)
385 385
386 386 def magic_gui(self, *args, **kwargs):
387 387 raise NotImplementedError(
388 388 'Kernel GUI support is not implemented yet, except for --pylab.')
389 389
390 390 def magic_pylab(self, *args, **kwargs):
391 391 raise NotImplementedError(
392 392 'pylab support must be enabled in command line options.')
393 393
394 394 # A few magics that are adapted to the specifics of using pexpect and a
395 395 # remote terminal
396 396
397 397 def magic_clear(self, arg_s):
398 398 """Clear the terminal."""
399 399 if os.name == 'posix':
400 400 self.shell.system("clear")
401 401 else:
402 402 self.shell.system("cls")
403 403
404 404 if os.name == 'nt':
405 405 # This is the usual name in windows
406 406 magic_cls = magic_clear
407 407
408 408 # Terminal pagers won't work over pexpect, but we do have our own pager
409 409
410 410 def magic_less(self, arg_s):
411 411 """Show a file through the pager.
412 412
413 413 Files ending in .py are syntax-highlighted."""
414 414 cont = open(arg_s).read()
415 415 if arg_s.endswith('.py'):
416 416 cont = self.shell.pycolorize(cont)
417 417 page.page(cont)
418 418
419 419 magic_more = magic_less
420 420
421 421 # Man calls a pager, so we also need to redefine it
422 422 if os.name == 'posix':
423 423 def magic_man(self, arg_s):
424 424 """Find the man page for the given command and display in pager."""
425 425 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
426 426 split=False))
427 427
428 428 # FIXME: this is specific to the GUI, so we should let the gui app load
429 429 # magics at startup that are only for the gui. Once the gui app has proper
430 430 # profile and configuration management, we can have it initialize a kernel
431 431 # with a special config file that provides these.
432 432 def magic_guiref(self, arg_s):
433 433 """Show a basic reference about the GUI console."""
434 434 from IPython.core.usage import gui_reference
435 435 page.page(gui_reference, auto_html=True)
436 436
437 437 def magic_connect_info(self, arg_s):
438 438 """Print information for connecting other clients to this kernel
439 439
440 440 It will print the contents of this session's connection file, as well as
441 441 shortcuts for local clients.
442 442
443 443 In the simplest case, when called from the most recently launched kernel,
444 444 secondary clients can be connected, simply with:
445 445
446 446 $> ipython <app> --existing
447 447
448 448 """
449 449 try:
450 450 connection_file = get_connection_file()
451 451 info = get_connection_info(unpack=False)
452 452 except Exception as e:
453 453 error("Could not get connection info: %r" % e)
454 454 return
455 455
456 456 print (info + '\n')
457 457 print ("Paste the above JSON into a file, and connect with:\n"
458 458 " $> ipython <app> --existing <file>\n"
459 459 "or, if you are local, you can connect with just:\n"
460 460 " $> ipython <app> --existing %s\n"
461 461 "or even just:\n"
462 462 " $> ipython <app> --existing\n"
463 463 "if this is the most recent IPython session you have started."
464 464 % os.path.basename(connection_file)
465 465 )
466 466
467 467 def magic_qtconsole(self, arg_s):
468 468 """Open a qtconsole connected to this kernel.
469 469
470 470 Useful for connecting a qtconsole to running notebooks, for better
471 471 debugging.
472 472 """
473 473 try:
474 p = connect_qtconsole(arg_split(arg_s, os.name=='posix'))
474 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
475 475 except Exception as e:
476 476 error("Could not start qtconsole: %r" % e)
477 477 return
478 478
479 479
480 480 def set_next_input(self, text):
481 481 """Send the specified text to the frontend to be presented at the next
482 482 input cell."""
483 483 payload = dict(
484 484 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
485 485 text=text
486 486 )
487 487 self.payload_manager.write_payload(payload)
488 488
489 489 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now