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