##// END OF EJS Templates
Fix another small bug in stripping kernel args...
MinRK -
Show More
@@ -1,595 +1,604
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 json
20 import json
21 import os
21 import os
22 import signal
22 import signal
23 import sys
23 import sys
24
24
25 # System library imports
25 # System library imports
26 from IPython.external.qt import QtGui
26 from IPython.external.qt import QtGui
27 from pygments.styles import get_all_styles
27 from pygments.styles import get_all_styles
28
28
29 # Local imports
29 # Local imports
30 from IPython.config.application import boolean_flag
30 from IPython.config.application import boolean_flag
31 from IPython.core.application import BaseIPythonApplication
31 from IPython.core.application import BaseIPythonApplication
32 from IPython.core.profiledir import ProfileDir
32 from IPython.core.profiledir import ProfileDir
33 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
33 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
34 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
34 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
35 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
35 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
36 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
36 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
37 from IPython.frontend.qt.console import styles
37 from IPython.frontend.qt.console import styles
38 from IPython.frontend.qt.kernelmanager import QtKernelManager
38 from IPython.frontend.qt.kernelmanager import QtKernelManager
39 from IPython.utils.path import filefind
39 from IPython.utils.path import filefind
40 from IPython.utils.py3compat import str_to_bytes
40 from IPython.utils.py3compat import str_to_bytes
41 from IPython.utils.traitlets import (
41 from IPython.utils.traitlets import (
42 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
42 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
43 )
43 )
44 from IPython.zmq.ipkernel import (
44 from IPython.zmq.ipkernel import (
45 flags as ipkernel_flags,
45 flags as ipkernel_flags,
46 aliases as ipkernel_aliases,
46 aliases as ipkernel_aliases,
47 IPKernelApp
47 IPKernelApp
48 )
48 )
49 from IPython.zmq.session import Session, default_secure
49 from IPython.zmq.session import Session, default_secure
50 from IPython.zmq.zmqshell import ZMQInteractiveShell
50 from IPython.zmq.zmqshell import ZMQInteractiveShell
51
51
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Network Constants
54 # Network Constants
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Globals
60 # Globals
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63 _examples = """
63 _examples = """
64 ipython qtconsole # start the qtconsole
64 ipython qtconsole # start the qtconsole
65 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
65 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
66 """
66 """
67
67
68 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
69 # Classes
69 # Classes
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71
71
72 class MainWindow(QtGui.QMainWindow):
72 class MainWindow(QtGui.QMainWindow):
73
73
74 #---------------------------------------------------------------------------
74 #---------------------------------------------------------------------------
75 # 'object' interface
75 # 'object' interface
76 #---------------------------------------------------------------------------
76 #---------------------------------------------------------------------------
77
77
78 def __init__(self, app, frontend, existing=False, may_close=True,
78 def __init__(self, app, frontend, existing=False, may_close=True,
79 confirm_exit=True):
79 confirm_exit=True):
80 """ Create a MainWindow for the specified FrontendWidget.
80 """ Create a MainWindow for the specified FrontendWidget.
81
81
82 The app is passed as an argument to allow for different
82 The app is passed as an argument to allow for different
83 closing behavior depending on whether we are the Kernel's parent.
83 closing behavior depending on whether we are the Kernel's parent.
84
84
85 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.
86
86
87 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
88 """
88 """
89 super(MainWindow, self).__init__()
89 super(MainWindow, self).__init__()
90 self._app = app
90 self._app = app
91 self._frontend = frontend
91 self._frontend = frontend
92 self._existing = existing
92 self._existing = existing
93 if existing:
93 if existing:
94 self._may_close = may_close
94 self._may_close = may_close
95 else:
95 else:
96 self._may_close = True
96 self._may_close = True
97 self._frontend.exit_requested.connect(self.close)
97 self._frontend.exit_requested.connect(self.close)
98 self._confirm_exit = confirm_exit
98 self._confirm_exit = confirm_exit
99 self.setCentralWidget(frontend)
99 self.setCentralWidget(frontend)
100
100
101 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
102 # QWidget interface
102 # QWidget interface
103 #---------------------------------------------------------------------------
103 #---------------------------------------------------------------------------
104
104
105 def closeEvent(self, event):
105 def closeEvent(self, event):
106 """ Close the window and the kernel (if necessary).
106 """ Close the window and the kernel (if necessary).
107
107
108 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
109 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,
110 it closes without prompt.
110 it closes without prompt.
111 """
111 """
112 keepkernel = None #Use the prompt by default
112 keepkernel = None #Use the prompt by default
113 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
114 keepkernel = self._frontend._keep_kernel_on_exit
114 keepkernel = self._frontend._keep_kernel_on_exit
115
115
116 kernel_manager = self._frontend.kernel_manager
116 kernel_manager = self._frontend.kernel_manager
117
117
118 if keepkernel is None and not self._confirm_exit:
118 if keepkernel is None and not self._confirm_exit:
119 # don't prompt, just terminate the kernel if we own it
119 # don't prompt, just terminate the kernel if we own it
120 # or leave it alone if we don't
120 # or leave it alone if we don't
121 keepkernel = not self._existing
121 keepkernel = not self._existing
122
122
123 if keepkernel is None: #show prompt
123 if keepkernel is None: #show prompt
124 if kernel_manager and kernel_manager.channels_running:
124 if kernel_manager and kernel_manager.channels_running:
125 title = self.window().windowTitle()
125 title = self.window().windowTitle()
126 cancel = QtGui.QMessageBox.Cancel
126 cancel = QtGui.QMessageBox.Cancel
127 okay = QtGui.QMessageBox.Ok
127 okay = QtGui.QMessageBox.Ok
128 if self._may_close:
128 if self._may_close:
129 msg = "You are closing this Console window."
129 msg = "You are closing this Console window."
130 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?"
131 justthis = QtGui.QPushButton("&No, just this Console", self)
131 justthis = QtGui.QPushButton("&No, just this Console", self)
132 justthis.setShortcut('N')
132 justthis.setShortcut('N')
133 closeall = QtGui.QPushButton("&Yes, quit everything", self)
133 closeall = QtGui.QPushButton("&Yes, quit everything", self)
134 closeall.setShortcut('Y')
134 closeall.setShortcut('Y')
135 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
135 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
136 title, msg)
136 title, msg)
137 box.setInformativeText(info)
137 box.setInformativeText(info)
138 box.addButton(cancel)
138 box.addButton(cancel)
139 box.addButton(justthis, QtGui.QMessageBox.NoRole)
139 box.addButton(justthis, QtGui.QMessageBox.NoRole)
140 box.addButton(closeall, QtGui.QMessageBox.YesRole)
140 box.addButton(closeall, QtGui.QMessageBox.YesRole)
141 box.setDefaultButton(closeall)
141 box.setDefaultButton(closeall)
142 box.setEscapeButton(cancel)
142 box.setEscapeButton(cancel)
143 reply = box.exec_()
143 reply = box.exec_()
144 if reply == 1: # close All
144 if reply == 1: # close All
145 kernel_manager.shutdown_kernel()
145 kernel_manager.shutdown_kernel()
146 #kernel_manager.stop_channels()
146 #kernel_manager.stop_channels()
147 event.accept()
147 event.accept()
148 elif reply == 0: # close Console
148 elif reply == 0: # close Console
149 if not self._existing:
149 if not self._existing:
150 # Have kernel: don't quit, just close the window
150 # Have kernel: don't quit, just close the window
151 self._app.setQuitOnLastWindowClosed(False)
151 self._app.setQuitOnLastWindowClosed(False)
152 self.deleteLater()
152 self.deleteLater()
153 event.accept()
153 event.accept()
154 else:
154 else:
155 event.ignore()
155 event.ignore()
156 else:
156 else:
157 reply = QtGui.QMessageBox.question(self, title,
157 reply = QtGui.QMessageBox.question(self, title,
158 "Are you sure you want to close this Console?"+
158 "Are you sure you want to close this Console?"+
159 "\nThe Kernel and other Consoles will remain active.",
159 "\nThe Kernel and other Consoles will remain active.",
160 okay|cancel,
160 okay|cancel,
161 defaultButton=okay
161 defaultButton=okay
162 )
162 )
163 if reply == okay:
163 if reply == okay:
164 event.accept()
164 event.accept()
165 else:
165 else:
166 event.ignore()
166 event.ignore()
167 elif keepkernel: #close console but leave kernel running (no prompt)
167 elif keepkernel: #close console but leave kernel running (no prompt)
168 if kernel_manager and kernel_manager.channels_running:
168 if kernel_manager and kernel_manager.channels_running:
169 if not self._existing:
169 if not self._existing:
170 # I have the kernel: don't quit, just close the window
170 # I have the kernel: don't quit, just close the window
171 self._app.setQuitOnLastWindowClosed(False)
171 self._app.setQuitOnLastWindowClosed(False)
172 event.accept()
172 event.accept()
173 else: #close console and kernel (no prompt)
173 else: #close console and kernel (no prompt)
174 if kernel_manager and kernel_manager.channels_running:
174 if kernel_manager and kernel_manager.channels_running:
175 kernel_manager.shutdown_kernel()
175 kernel_manager.shutdown_kernel()
176 event.accept()
176 event.accept()
177
177
178 #-----------------------------------------------------------------------------
178 #-----------------------------------------------------------------------------
179 # Aliases and Flags
179 # Aliases and Flags
180 #-----------------------------------------------------------------------------
180 #-----------------------------------------------------------------------------
181
181
182 flags = dict(ipkernel_flags)
182 flags = dict(ipkernel_flags)
183 qt_flags = {
183 qt_flags = {
184 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
184 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
185 "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"),
186 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
186 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
187 "Use a pure Python kernel instead of an IPython kernel."),
187 "Use a pure Python kernel instead of an IPython kernel."),
188 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
188 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
189 "Disable rich text support."),
189 "Disable rich text support."),
190 }
190 }
191 qt_flags.update(boolean_flag(
191 qt_flags.update(boolean_flag(
192 'gui-completion', 'ConsoleWidget.gui_completion',
192 'gui-completion', 'ConsoleWidget.gui_completion',
193 "use a GUI widget for tab completion",
193 "use a GUI widget for tab completion",
194 "use plaintext output for completion"
194 "use plaintext output for completion"
195 ))
195 ))
196 qt_flags.update(boolean_flag(
196 qt_flags.update(boolean_flag(
197 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
197 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
198 """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',
199 to force a direct exit without any confirmation.
199 to force a direct exit without any confirmation.
200 """,
200 """,
201 """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
202 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.
203 """
203 """
204 ))
204 ))
205 flags.update(qt_flags)
205 flags.update(qt_flags)
206
206
207 aliases = dict(ipkernel_aliases)
207 aliases = dict(ipkernel_aliases)
208
208
209 qt_aliases = dict(
209 qt_aliases = dict(
210 hb = 'IPythonQtConsoleApp.hb_port',
210 hb = 'IPythonQtConsoleApp.hb_port',
211 shell = 'IPythonQtConsoleApp.shell_port',
211 shell = 'IPythonQtConsoleApp.shell_port',
212 iopub = 'IPythonQtConsoleApp.iopub_port',
212 iopub = 'IPythonQtConsoleApp.iopub_port',
213 stdin = 'IPythonQtConsoleApp.stdin_port',
213 stdin = 'IPythonQtConsoleApp.stdin_port',
214 ip = 'IPythonQtConsoleApp.ip',
214 ip = 'IPythonQtConsoleApp.ip',
215 existing = 'IPythonQtConsoleApp.existing',
215 existing = 'IPythonQtConsoleApp.existing',
216 f = 'IPythonQtConsoleApp.connection_file',
216 f = 'IPythonQtConsoleApp.connection_file',
217
217
218 style = 'IPythonWidget.syntax_style',
218 style = 'IPythonWidget.syntax_style',
219 stylesheet = 'IPythonQtConsoleApp.stylesheet',
219 stylesheet = 'IPythonQtConsoleApp.stylesheet',
220 colors = 'ZMQInteractiveShell.colors',
220 colors = 'ZMQInteractiveShell.colors',
221
221
222 editor = 'IPythonWidget.editor',
222 editor = 'IPythonWidget.editor',
223 paging = 'ConsoleWidget.paging',
223 paging = 'ConsoleWidget.paging',
224 ssh = 'IPythonQtConsoleApp.sshserver',
224 ssh = 'IPythonQtConsoleApp.sshserver',
225 )
225 )
226 aliases.update(qt_aliases)
226 aliases.update(qt_aliases)
227
227
228
228
229 #-----------------------------------------------------------------------------
229 #-----------------------------------------------------------------------------
230 # IPythonQtConsole
230 # IPythonQtConsole
231 #-----------------------------------------------------------------------------
231 #-----------------------------------------------------------------------------
232
232
233
233
234 class IPythonQtConsoleApp(BaseIPythonApplication):
234 class IPythonQtConsoleApp(BaseIPythonApplication):
235 name = 'ipython-qtconsole'
235 name = 'ipython-qtconsole'
236 default_config_file_name='ipython_config.py'
236 default_config_file_name='ipython_config.py'
237
237
238 description = """
238 description = """
239 The IPython QtConsole.
239 The IPython QtConsole.
240
240
241 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
242 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
243 input.
243 input.
244
244
245 The QtConsole supports various extra features beyond the Terminal IPython
245 The QtConsole supports various extra features beyond the Terminal IPython
246 shell, such as inline plotting with matplotlib, via:
246 shell, such as inline plotting with matplotlib, via:
247
247
248 ipython qtconsole --pylab=inline
248 ipython qtconsole --pylab=inline
249
249
250 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.
251
251
252 """
252 """
253 examples = _examples
253 examples = _examples
254
254
255 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
255 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
256 flags = Dict(flags)
256 flags = Dict(flags)
257 aliases = Dict(aliases)
257 aliases = Dict(aliases)
258
258
259 kernel_argv = List(Unicode)
259 kernel_argv = List(Unicode)
260
260
261 # create requested profiles by default, if they don't exist:
261 # create requested profiles by default, if they don't exist:
262 auto_create = CBool(True)
262 auto_create = CBool(True)
263 # connection info:
263 # connection info:
264 ip = Unicode(LOCALHOST, config=True,
264 ip = Unicode(LOCALHOST, config=True,
265 help="""Set the kernel\'s IP address [default localhost].
265 help="""Set the kernel\'s IP address [default localhost].
266 If the IP address is something other than localhost, then
266 If the IP address is something other than localhost, then
267 Consoles on other machines will be able to connect
267 Consoles on other machines will be able to connect
268 to the Kernel, so be careful!"""
268 to the Kernel, so be careful!"""
269 )
269 )
270
270
271 sshserver = Unicode('', config=True,
271 sshserver = Unicode('', config=True,
272 help="""The SSH server to use to connect to the kernel.""")
272 help="""The SSH server to use to connect to the kernel.""")
273 sshkey = Unicode('', config=True,
273 sshkey = Unicode('', config=True,
274 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.""")
275
275
276 hb_port = Int(0, config=True,
276 hb_port = Int(0, config=True,
277 help="set the heartbeat port [default: random]")
277 help="set the heartbeat port [default: random]")
278 shell_port = Int(0, config=True,
278 shell_port = Int(0, config=True,
279 help="set the shell (XREP) port [default: random]")
279 help="set the shell (XREP) port [default: random]")
280 iopub_port = Int(0, config=True,
280 iopub_port = Int(0, config=True,
281 help="set the iopub (PUB) port [default: random]")
281 help="set the iopub (PUB) port [default: random]")
282 stdin_port = Int(0, config=True,
282 stdin_port = Int(0, config=True,
283 help="set the stdin (XREQ) port [default: random]")
283 help="set the stdin (XREQ) port [default: random]")
284 connection_file = Unicode('', config=True,
284 connection_file = Unicode('', config=True,
285 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]
286
286
287 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
288 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
289 of the current profile, but can be specified by absolute path.
289 of the current profile, but can be specified by absolute path.
290 """)
290 """)
291 def _connection_file_default(self):
291 def _connection_file_default(self):
292 return 'kernel-%i.json' % os.getpid()
292 return 'kernel-%i.json' % os.getpid()
293
293
294 existing = Unicode('', config=True,
294 existing = Unicode('', config=True,
295 help="""Connect to an already running kernel""")
295 help="""Connect to an already running kernel""")
296
296
297 stylesheet = Unicode('', config=True,
297 stylesheet = Unicode('', config=True,
298 help="path to a custom CSS stylesheet")
298 help="path to a custom CSS stylesheet")
299
299
300 pure = CBool(False, config=True,
300 pure = CBool(False, config=True,
301 help="Use a pure Python kernel instead of an IPython kernel.")
301 help="Use a pure Python kernel instead of an IPython kernel.")
302 plain = CBool(False, config=True,
302 plain = CBool(False, config=True,
303 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).")
304
304
305 def _pure_changed(self, name, old, new):
305 def _pure_changed(self, name, old, new):
306 kind = 'plain' if self.plain else 'rich'
306 kind = 'plain' if self.plain else 'rich'
307 self.config.ConsoleWidget.kind = kind
307 self.config.ConsoleWidget.kind = kind
308 if self.pure:
308 if self.pure:
309 self.widget_factory = FrontendWidget
309 self.widget_factory = FrontendWidget
310 elif self.plain:
310 elif self.plain:
311 self.widget_factory = IPythonWidget
311 self.widget_factory = IPythonWidget
312 else:
312 else:
313 self.widget_factory = RichIPythonWidget
313 self.widget_factory = RichIPythonWidget
314
314
315 _plain_changed = _pure_changed
315 _plain_changed = _pure_changed
316
316
317 confirm_exit = CBool(True, config=True,
317 confirm_exit = CBool(True, config=True,
318 help="""
318 help="""
319 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',
320 to force a direct exit without any confirmation.""",
320 to force a direct exit without any confirmation.""",
321 )
321 )
322
322
323 # the factory for creating a widget
323 # the factory for creating a widget
324 widget_factory = Any(RichIPythonWidget)
324 widget_factory = Any(RichIPythonWidget)
325
325
326 def parse_command_line(self, argv=None):
326 def parse_command_line(self, argv=None):
327 super(IPythonQtConsoleApp, self).parse_command_line(argv)
327 super(IPythonQtConsoleApp, self).parse_command_line(argv)
328 if argv is None:
328 if argv is None:
329 argv = sys.argv[1:]
329 argv = sys.argv[1:]
330
331 self.kernel_argv = list(argv) # copy
330 self.kernel_argv = list(argv) # copy
332 # kernel should inherit default config file from frontend
331 # kernel should inherit default config file from frontend
333 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
332 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
334 # Scrub frontend-specific flags
333 # Scrub frontend-specific flags
335 for a in argv:
336 if a.startswith('-') and a.lstrip('-') in qt_flags:
337 self.kernel_argv.remove(a)
338 swallow_next = False
334 swallow_next = False
335 was_flag = False
336 # copy again, in case some aliases have the same name as a flag
337 # argv = list(self.kernel_argv)
339 for a in argv:
338 for a in argv:
340 if swallow_next:
339 if swallow_next:
341 self.kernel_argv.remove(a)
342 swallow_next = False
340 swallow_next = False
341 # last arg was an alias, remove the next one
342 # *unless* the last alias has a no-arg flag version, in which
343 # case, don't swallow the next arg if it's also a flag:
344 if not (was_flag and a.startswith('-')):
345 self.kernel_argv.remove(a)
343 continue
346 continue
344 if a.startswith('-'):
347 if a.startswith('-'):
345 split = a.lstrip('-').split('=')
348 split = a.lstrip('-').split('=')
346 alias = split[0]
349 alias = split[0]
347 if alias in qt_aliases:
350 if alias in qt_aliases:
348 self.kernel_argv.remove(a)
351 self.kernel_argv.remove(a)
349 if len(split) == 1:
352 if len(split) == 1:
350 # alias passed with arg via space
353 # alias passed with arg via space
351 swallow_next = True
354 swallow_next = True
355 # could have been a flag that matches an alias, e.g. `existing`
356 # in which case, we might not swallow the next arg
357 was_flag = alias in qt_flags
358 elif alias in qt_flags:
359 # strip flag, but don't swallow next, as flags don't take args
360 self.kernel_argv.remove(a)
352
361
353 def init_connection_file(self):
362 def init_connection_file(self):
354 """find the connection file, and load the info if found.
363 """find the connection file, and load the info if found.
355
364
356 The current working directory and the current profile's security
365 The current working directory and the current profile's security
357 directory will be searched for the file if it is not given by
366 directory will be searched for the file if it is not given by
358 absolute path.
367 absolute path.
359
368
360 When attempting to connect to an existing kernel and the `--existing`
369 When attempting to connect to an existing kernel and the `--existing`
361 argument does not match an existing file, it will be interpreted as a
370 argument does not match an existing file, it will be interpreted as a
362 fileglob, and the matching file in the current profile's security dir
371 fileglob, and the matching file in the current profile's security dir
363 with the latest access time will be used.
372 with the latest access time will be used.
364 """
373 """
365 if self.existing:
374 if self.existing:
366 try:
375 try:
367 cf = find_connection_file(self.existing)
376 cf = find_connection_file(self.existing)
368 except Exception:
377 except Exception:
369 self.log.critical("Could not find existing kernel connection file %s", self.existing)
378 self.log.critical("Could not find existing kernel connection file %s", self.existing)
370 self.exit(1)
379 self.exit(1)
371 self.log.info("Connecting to existing kernel: %s" % cf)
380 self.log.info("Connecting to existing kernel: %s" % cf)
372 self.connection_file = cf
381 self.connection_file = cf
373 # should load_connection_file only be used for existing?
382 # should load_connection_file only be used for existing?
374 # as it is now, this allows reusing ports if an existing
383 # as it is now, this allows reusing ports if an existing
375 # file is requested
384 # file is requested
376 try:
385 try:
377 self.load_connection_file()
386 self.load_connection_file()
378 except Exception:
387 except Exception:
379 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
388 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
380 self.exit(1)
389 self.exit(1)
381
390
382 def load_connection_file(self):
391 def load_connection_file(self):
383 """load ip/port/hmac config from JSON connection file"""
392 """load ip/port/hmac config from JSON connection file"""
384 # this is identical to KernelApp.load_connection_file
393 # this is identical to KernelApp.load_connection_file
385 # perhaps it can be centralized somewhere?
394 # perhaps it can be centralized somewhere?
386 try:
395 try:
387 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
396 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
388 except IOError:
397 except IOError:
389 self.log.debug("Connection File not found: %s", self.connection_file)
398 self.log.debug("Connection File not found: %s", self.connection_file)
390 return
399 return
391 self.log.debug(u"Loading connection file %s", fname)
400 self.log.debug(u"Loading connection file %s", fname)
392 with open(fname) as f:
401 with open(fname) as f:
393 s = f.read()
402 s = f.read()
394 cfg = json.loads(s)
403 cfg = json.loads(s)
395 if self.ip == LOCALHOST and 'ip' in cfg:
404 if self.ip == LOCALHOST and 'ip' in cfg:
396 # not overridden by config or cl_args
405 # not overridden by config or cl_args
397 self.ip = cfg['ip']
406 self.ip = cfg['ip']
398 for channel in ('hb', 'shell', 'iopub', 'stdin'):
407 for channel in ('hb', 'shell', 'iopub', 'stdin'):
399 name = channel + '_port'
408 name = channel + '_port'
400 if getattr(self, name) == 0 and name in cfg:
409 if getattr(self, name) == 0 and name in cfg:
401 # not overridden by config or cl_args
410 # not overridden by config or cl_args
402 setattr(self, name, cfg[name])
411 setattr(self, name, cfg[name])
403 if 'key' in cfg:
412 if 'key' in cfg:
404 self.config.Session.key = str_to_bytes(cfg['key'])
413 self.config.Session.key = str_to_bytes(cfg['key'])
405
414
406 def init_ssh(self):
415 def init_ssh(self):
407 """set up ssh tunnels, if needed."""
416 """set up ssh tunnels, if needed."""
408 if not self.sshserver and not self.sshkey:
417 if not self.sshserver and not self.sshkey:
409 return
418 return
410
419
411 if self.sshkey and not self.sshserver:
420 if self.sshkey and not self.sshserver:
412 # specifying just the key implies that we are connecting directly
421 # specifying just the key implies that we are connecting directly
413 self.sshserver = self.ip
422 self.sshserver = self.ip
414 self.ip = LOCALHOST
423 self.ip = LOCALHOST
415
424
416 # build connection dict for tunnels:
425 # build connection dict for tunnels:
417 info = dict(ip=self.ip,
426 info = dict(ip=self.ip,
418 shell_port=self.shell_port,
427 shell_port=self.shell_port,
419 iopub_port=self.iopub_port,
428 iopub_port=self.iopub_port,
420 stdin_port=self.stdin_port,
429 stdin_port=self.stdin_port,
421 hb_port=self.hb_port
430 hb_port=self.hb_port
422 )
431 )
423
432
424 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
433 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
425
434
426 # tunnels return a new set of ports, which will be on localhost:
435 # tunnels return a new set of ports, which will be on localhost:
427 self.ip = LOCALHOST
436 self.ip = LOCALHOST
428 try:
437 try:
429 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
438 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
430 except:
439 except:
431 # even catch KeyboardInterrupt
440 # even catch KeyboardInterrupt
432 self.log.error("Could not setup tunnels", exc_info=True)
441 self.log.error("Could not setup tunnels", exc_info=True)
433 self.exit(1)
442 self.exit(1)
434
443
435 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
444 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
436
445
437 cf = self.connection_file
446 cf = self.connection_file
438 base,ext = os.path.splitext(cf)
447 base,ext = os.path.splitext(cf)
439 base = os.path.basename(base)
448 base = os.path.basename(base)
440 self.connection_file = os.path.basename(base)+'-ssh'+ext
449 self.connection_file = os.path.basename(base)+'-ssh'+ext
441 self.log.critical("To connect another client via this tunnel, use:")
450 self.log.critical("To connect another client via this tunnel, use:")
442 self.log.critical("--existing %s" % self.connection_file)
451 self.log.critical("--existing %s" % self.connection_file)
443
452
444 def init_kernel_manager(self):
453 def init_kernel_manager(self):
445 # Don't let Qt or ZMQ swallow KeyboardInterupts.
454 # Don't let Qt or ZMQ swallow KeyboardInterupts.
446 signal.signal(signal.SIGINT, signal.SIG_DFL)
455 signal.signal(signal.SIGINT, signal.SIG_DFL)
447 sec = self.profile_dir.security_dir
456 sec = self.profile_dir.security_dir
448 try:
457 try:
449 cf = filefind(self.connection_file, ['.', sec])
458 cf = filefind(self.connection_file, ['.', sec])
450 except IOError:
459 except IOError:
451 # file might not exist
460 # file might not exist
452 if self.connection_file == os.path.basename(self.connection_file):
461 if self.connection_file == os.path.basename(self.connection_file):
453 # just shortname, put it in security dir
462 # just shortname, put it in security dir
454 cf = os.path.join(sec, self.connection_file)
463 cf = os.path.join(sec, self.connection_file)
455 else:
464 else:
456 cf = self.connection_file
465 cf = self.connection_file
457
466
458 # Create a KernelManager and start a kernel.
467 # Create a KernelManager and start a kernel.
459 self.kernel_manager = QtKernelManager(
468 self.kernel_manager = QtKernelManager(
460 ip=self.ip,
469 ip=self.ip,
461 shell_port=self.shell_port,
470 shell_port=self.shell_port,
462 iopub_port=self.iopub_port,
471 iopub_port=self.iopub_port,
463 stdin_port=self.stdin_port,
472 stdin_port=self.stdin_port,
464 hb_port=self.hb_port,
473 hb_port=self.hb_port,
465 connection_file=cf,
474 connection_file=cf,
466 config=self.config,
475 config=self.config,
467 )
476 )
468 # start the kernel
477 # start the kernel
469 if not self.existing:
478 if not self.existing:
470 kwargs = dict(ipython=not self.pure)
479 kwargs = dict(ipython=not self.pure)
471 kwargs['extra_arguments'] = self.kernel_argv
480 kwargs['extra_arguments'] = self.kernel_argv
472 self.kernel_manager.start_kernel(**kwargs)
481 self.kernel_manager.start_kernel(**kwargs)
473 elif self.sshserver:
482 elif self.sshserver:
474 # ssh, write new connection file
483 # ssh, write new connection file
475 self.kernel_manager.write_connection_file()
484 self.kernel_manager.write_connection_file()
476 self.kernel_manager.start_channels()
485 self.kernel_manager.start_channels()
477
486
478
487
479 def init_qt_elements(self):
488 def init_qt_elements(self):
480 # Create the widget.
489 # Create the widget.
481 self.app = QtGui.QApplication([])
490 self.app = QtGui.QApplication([])
482 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
491 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
483 self.widget = self.widget_factory(config=self.config,
492 self.widget = self.widget_factory(config=self.config,
484 local_kernel=local_kernel)
493 local_kernel=local_kernel)
485 self.widget.kernel_manager = self.kernel_manager
494 self.widget.kernel_manager = self.kernel_manager
486 self.window = MainWindow(self.app, self.widget, self.existing,
495 self.window = MainWindow(self.app, self.widget, self.existing,
487 may_close=local_kernel,
496 may_close=local_kernel,
488 confirm_exit=self.confirm_exit)
497 confirm_exit=self.confirm_exit)
489 self.window.setWindowTitle('Python' if self.pure else 'IPython')
498 self.window.setWindowTitle('Python' if self.pure else 'IPython')
490
499
491 def init_colors(self):
500 def init_colors(self):
492 """Configure the coloring of the widget"""
501 """Configure the coloring of the widget"""
493 # Note: This will be dramatically simplified when colors
502 # Note: This will be dramatically simplified when colors
494 # are removed from the backend.
503 # are removed from the backend.
495
504
496 if self.pure:
505 if self.pure:
497 # only IPythonWidget supports styling
506 # only IPythonWidget supports styling
498 return
507 return
499
508
500 # parse the colors arg down to current known labels
509 # parse the colors arg down to current known labels
501 try:
510 try:
502 colors = self.config.ZMQInteractiveShell.colors
511 colors = self.config.ZMQInteractiveShell.colors
503 except AttributeError:
512 except AttributeError:
504 colors = None
513 colors = None
505 try:
514 try:
506 style = self.config.IPythonWidget.syntax_style
515 style = self.config.IPythonWidget.syntax_style
507 except AttributeError:
516 except AttributeError:
508 style = None
517 style = None
509
518
510 # find the value for colors:
519 # find the value for colors:
511 if colors:
520 if colors:
512 colors=colors.lower()
521 colors=colors.lower()
513 if colors in ('lightbg', 'light'):
522 if colors in ('lightbg', 'light'):
514 colors='lightbg'
523 colors='lightbg'
515 elif colors in ('dark', 'linux'):
524 elif colors in ('dark', 'linux'):
516 colors='linux'
525 colors='linux'
517 else:
526 else:
518 colors='nocolor'
527 colors='nocolor'
519 elif style:
528 elif style:
520 if style=='bw':
529 if style=='bw':
521 colors='nocolor'
530 colors='nocolor'
522 elif styles.dark_style(style):
531 elif styles.dark_style(style):
523 colors='linux'
532 colors='linux'
524 else:
533 else:
525 colors='lightbg'
534 colors='lightbg'
526 else:
535 else:
527 colors=None
536 colors=None
528
537
529 # Configure the style.
538 # Configure the style.
530 widget = self.widget
539 widget = self.widget
531 if style:
540 if style:
532 widget.style_sheet = styles.sheet_from_template(style, colors)
541 widget.style_sheet = styles.sheet_from_template(style, colors)
533 widget.syntax_style = style
542 widget.syntax_style = style
534 widget._syntax_style_changed()
543 widget._syntax_style_changed()
535 widget._style_sheet_changed()
544 widget._style_sheet_changed()
536 elif colors:
545 elif colors:
537 # use a default style
546 # use a default style
538 widget.set_default_style(colors=colors)
547 widget.set_default_style(colors=colors)
539 else:
548 else:
540 # this is redundant for now, but allows the widget's
549 # this is redundant for now, but allows the widget's
541 # defaults to change
550 # defaults to change
542 widget.set_default_style()
551 widget.set_default_style()
543
552
544 if self.stylesheet:
553 if self.stylesheet:
545 # we got an expicit stylesheet
554 # we got an expicit stylesheet
546 if os.path.isfile(self.stylesheet):
555 if os.path.isfile(self.stylesheet):
547 with open(self.stylesheet) as f:
556 with open(self.stylesheet) as f:
548 sheet = f.read()
557 sheet = f.read()
549 widget.style_sheet = sheet
558 widget.style_sheet = sheet
550 widget._style_sheet_changed()
559 widget._style_sheet_changed()
551 else:
560 else:
552 raise IOError("Stylesheet %r not found."%self.stylesheet)
561 raise IOError("Stylesheet %r not found."%self.stylesheet)
553
562
554 def initialize(self, argv=None):
563 def initialize(self, argv=None):
555 super(IPythonQtConsoleApp, self).initialize(argv)
564 super(IPythonQtConsoleApp, self).initialize(argv)
556 self.init_connection_file()
565 self.init_connection_file()
557 default_secure(self.config)
566 default_secure(self.config)
558 self.init_ssh()
567 self.init_ssh()
559 self.init_kernel_manager()
568 self.init_kernel_manager()
560 self.init_qt_elements()
569 self.init_qt_elements()
561 self.init_colors()
570 self.init_colors()
562 self.init_window_shortcut()
571 self.init_window_shortcut()
563
572
564 def init_window_shortcut(self):
573 def init_window_shortcut(self):
565 fullScreenAction = QtGui.QAction('Toggle Full Screen', self.window)
574 fullScreenAction = QtGui.QAction('Toggle Full Screen', self.window)
566 fullScreenAction.setShortcut('Ctrl+Meta+Space')
575 fullScreenAction.setShortcut('Ctrl+Meta+Space')
567 fullScreenAction.triggered.connect(self.toggleFullScreen)
576 fullScreenAction.triggered.connect(self.toggleFullScreen)
568 self.window.addAction(fullScreenAction)
577 self.window.addAction(fullScreenAction)
569
578
570 def toggleFullScreen(self):
579 def toggleFullScreen(self):
571 if not self.window.isFullScreen():
580 if not self.window.isFullScreen():
572 self.window.showFullScreen()
581 self.window.showFullScreen()
573 else:
582 else:
574 self.window.showNormal()
583 self.window.showNormal()
575
584
576 def start(self):
585 def start(self):
577
586
578 # draw the window
587 # draw the window
579 self.window.show()
588 self.window.show()
580
589
581 # Start the application main loop.
590 # Start the application main loop.
582 self.app.exec_()
591 self.app.exec_()
583
592
584 #-----------------------------------------------------------------------------
593 #-----------------------------------------------------------------------------
585 # Main entry point
594 # Main entry point
586 #-----------------------------------------------------------------------------
595 #-----------------------------------------------------------------------------
587
596
588 def main():
597 def main():
589 app = IPythonQtConsoleApp()
598 app = IPythonQtConsoleApp()
590 app.initialize()
599 app.initialize()
591 app.start()
600 app.start()
592
601
593
602
594 if __name__ == '__main__':
603 if __name__ == '__main__':
595 main()
604 main()
General Comments 0
You need to be logged in to leave comments. Login now