##// END OF EJS Templates
decamelcasify: a few more
Matthias BUSSONNIER -
Show More
@@ -1,1095 +1,1095 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib imports
20 20 import json
21 21 import os
22 22 import signal
23 23 import sys
24 24
25 25 # System library imports
26 26 from IPython.external.qt import QtGui,QtCore
27 27 from pygments.styles import get_all_styles
28 28
29 29 # Local imports
30 30 from IPython.config.application import boolean_flag
31 31 from IPython.core.application import BaseIPythonApplication
32 32 from IPython.core.profiledir import ProfileDir
33 33 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
34 34 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
35 35 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
36 36 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
37 37 from IPython.frontend.qt.console import styles
38 38 from IPython.frontend.qt.kernelmanager import QtKernelManager
39 39 from IPython.utils.path import filefind
40 40 from IPython.utils.py3compat import str_to_bytes
41 41 from IPython.utils.traitlets import (
42 42 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
43 43 )
44 44 from IPython.zmq.ipkernel import (
45 45 flags as ipkernel_flags,
46 46 aliases as ipkernel_aliases,
47 47 IPKernelApp
48 48 )
49 49 from IPython.zmq.session import Session, default_secure
50 50 from IPython.zmq.zmqshell import ZMQInteractiveShell
51 51
52 52 import application_rc
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Network Constants
56 56 #-----------------------------------------------------------------------------
57 57
58 58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Globals
62 62 #-----------------------------------------------------------------------------
63 63
64 64 _examples = """
65 65 ipython qtconsole # start the qtconsole
66 66 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 67 """
68 68
69 69 #-----------------------------------------------------------------------------
70 70 # Classes
71 71 #-----------------------------------------------------------------------------
72 72
73 73 class MainWindow(QtGui.QMainWindow):
74 74
75 75 #---------------------------------------------------------------------------
76 76 # 'object' interface
77 77 #---------------------------------------------------------------------------
78 78
79 79 def __init__(self, app, frontend, existing=False, may_close=True,
80 80 confirm_exit=True):
81 81 """ Create a MainWindow for the specified FrontendWidget.
82 82
83 83 The app is passed as an argument to allow for different
84 84 closing behavior depending on whether we are the Kernel's parent.
85 85
86 86 If existing is True, then this Console does not own the Kernel.
87 87
88 88 If may_close is True, then this Console is permitted to close the kernel
89 89 """
90 90
91 91 super(MainWindow, self).__init__()
92 92 self._app = app
93 93
94 94 self.tab_widget = QtGui.QTabWidget(self)
95 95 self.tab_widget.setDocumentMode(True)
96 96 self.tab_widget.setTabsClosable(True)
97 97 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
98 98
99 99 self.setCentralWidget(self.tab_widget)
100 100 self.update_tab_bar_visibility()
101 101
102 102 def update_tab_bar_visibility(self):
103 103 """ update visibility of the tabBar depending of the number of tab
104 104
105 105 0 or 1 tab, tabBar hidden
106 106 2+ tabs, tabBar visible
107 107
108 108 send a self.close if number of tab ==0
109 109
110 110 need to be called explicitely, or be connected to tabInserted/tabRemoved
111 111 """
112 112 if self.tab_widget.count() <= 1:
113 113 self.tab_widget.tabBar().setVisible(False)
114 114 else:
115 115 self.tab_widget.tabBar().setVisible(True)
116 116 if self.tab_widget.count()==0 :
117 117 self.close()
118 118
119 119 @property
120 120 def active_frontend(self):
121 121 return self.tab_widget.currentWidget()
122 122
123 def close_tab(self,currentTab):
123 def close_tab(self,current_tab):
124 124 """ Called when you need to try to close a tab.
125 125
126 126 It takes the number of the tab to be closed as argument, or a referece
127 127 to the wiget insite this tab
128 128 """
129 129
130 130 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
131 131 # and a reference to the trontend to close
132 if type(currentTab) is not int :
133 currentTab = self.tab_widget.indexOf(currentTab)
134 closing_widget=self.tab_widget.widget(currentTab)
132 if type(current_tab) is not int :
133 current_tab = self.tab_widget.indexOf(current_tab)
134 closing_widget=self.tab_widget.widget(current_tab)
135 135
136 136
137 137 # when trying to be closed, widget might re-send a request to be closed again, but will
138 138 # be deleted when event will be processed. So need to check that widget still exist and
139 139 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
140 140 # re-send by this fonction on the master widget, which ask all slaves widget to exit
141 141 if closing_widget==None:
142 142 return
143 143
144 144 #get a list of all wwidget not owning the kernel.
145 slaveTabs=self.findSlavesTabs(closing_widget)
145 slave_tabs=self.findSlavesTabs(closing_widget)
146 146
147 147 keepkernel = None #Use the prompt by default
148 148 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
149 149 keepkernel = closing_widget._keep_kernel_on_exit
150 150 # If signal sent by exist magic (_keep_kernel_on_exit, exist and not None)
151 151 # we set local slave tabs._hidden to True to avoit prompting for kernel
152 152 # restart when they litt get the signal. and the "forward" the 'exit'
153 153 # to the main win
154 154 if keepkernel is not None:
155 for tab in slaveTabs:
155 for tab in slave_tabs:
156 156 tab._hidden = True
157 if closing_widget in slaveTabs :
157 if closing_widget in slave_tabs :
158 158 try :
159 self.findMasterTab(closing_widget).execute('exit')
159 self.find_master_tab(closing_widget).execute('exit')
160 160 except AttributeError:
161 161 self.log.info("Master already closed or not local, closing only current tab")
162 self.tab_widget.removeTab(currentTab)
162 self.tab_widget.removeTab(current_tab)
163 163 return
164 164
165 165 kernel_manager = closing_widget.kernel_manager
166 166
167 167 if keepkernel is None and not closing_widget._confirm_exit:
168 168 # don't prompt, just terminate the kernel if we own it
169 169 # or leave it alone if we don't
170 170 keepkernel = not closing_widget._existing
171 171
172 172 if keepkernel is None: #show prompt
173 173 if kernel_manager and kernel_manager.channels_running:
174 174 title = self.window().windowTitle()
175 175 cancel = QtGui.QMessageBox.Cancel
176 176 okay = QtGui.QMessageBox.Ok
177 177 if closing_widget._may_close:
178 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(currentTab)+'"'
178 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
179 179 info = "Would you like to quit the Kernel and all attached Consoles as well?"
180 180 justthis = QtGui.QPushButton("&No, just this Console", self)
181 181 justthis.setShortcut('N')
182 182 closeall = QtGui.QPushButton("&Yes, quit everything", self)
183 183 closeall.setShortcut('Y')
184 184 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
185 185 title, msg)
186 186 box.setInformativeText(info)
187 187 box.addButton(cancel)
188 188 box.addButton(justthis, QtGui.QMessageBox.NoRole)
189 189 box.addButton(closeall, QtGui.QMessageBox.YesRole)
190 190 box.setDefaultButton(closeall)
191 191 box.setEscapeButton(cancel)
192 192 pixmap = QtGui.QPixmap(':/icon/IPythonConsole.png')
193 193 scaledpixmap = pixmap.scaledToWidth(64,mode=QtCore.Qt.SmoothTransformation)
194 194 box.setIconPixmap(scaledpixmap)
195 195 reply = box.exec_()
196 196 if reply == 1: # close All
197 for slave in slaveTabs:
197 for slave in slave_tabs:
198 198 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
199 199 closing_widget.execute("exit")
200 self.tab_widget.removeTab(currentTab)
200 self.tab_widget.removeTab(current_tab)
201 201 elif reply == 0: # close Console
202 202 if not closing_widget._existing:
203 203 # Have kernel: don't quit, just close the window
204 204 self._app.setQuitOnLastWindowClosed(False)
205 205 closing_widget.execute("exit True")
206 206 else:
207 207 reply = QtGui.QMessageBox.question(self, title,
208 208 "Are you sure you want to close this Console?"+
209 209 "\nThe Kernel and other Consoles will remain active.",
210 210 okay|cancel,
211 211 defaultButton=okay
212 212 )
213 213 if reply == okay:
214 self.tab_widget.removeTab(currentTab)
214 self.tab_widget.removeTab(current_tab)
215 215 elif keepkernel: #close console but leave kernel running (no prompt)
216 216 if kernel_manager and kernel_manager.channels_running:
217 217 if not closing_widget._existing:
218 218 # I have the kernel: don't quit, just close the window
219 self.tab_widget.removeTab(currentTab)
219 self.tab_widget.removeTab(current_tab)
220 220 else: #close console and kernel (no prompt)
221 221 if kernel_manager and kernel_manager.channels_running:
222 for slave in slaveTabs:
222 for slave in slave_tabs:
223 223 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
224 self.tab_widget.removeTab(currentTab)
224 self.tab_widget.removeTab(current_tab)
225 225 kernel_manager.shutdown_kernel()
226 226 self.update_tab_bar_visibility()
227 227
228 def addTabWithFrontend(self,frontend,name=None):
228 def add_tab_with_frontend(self,frontend,name=None):
229 229 """ insert a tab with a given frontend in the tab bar, and give it a name
230 230
231 231 """
232 232 if not name:
233 233 name=str('kernel '+str(self.tab_widget.count()))
234 234 self.tab_widget.addTab(frontend,name)
235 235 self.update_tab_bar_visibility()
236 self.makeFrontendVisible(frontend)
236 self.make_frontend_visible(frontend)
237 237 frontend.exit_requested.connect(self.close_tab)
238 238
239 239 def next_tab(self):
240 240 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
241 241
242 242 def prev_tab(self):
243 243 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
244 244
245 def makeFrontendVisible(self,frontend):
245 def make_frontend_visible(self,frontend):
246 246 widgetIndex=self.tab_widget.indexOf(frontend)
247 247 if widgetIndex > 0 :
248 248 self.tab_widget.setCurrentIndex(widgetIndex)
249 249
250 def findMasterTab(self,tab,asList=False):
250 def find_master_tab(self,tab,asList=False):
251 251 """
252 252 Try to return the frontend that own the kernel attached to the given widget/tab.
253 253
254 254 Only find frontend owed by the current application. Selection
255 255 based on port of the kernel, might be inacurate if several kernel
256 256 on different ip use same port number.
257 257
258 258 This fonction does the conversion tabNumber/widget if needed.
259 259 Might return None if no master widget (non local kernel)
260 260 Will crash IPython if more than 1 masterWidget
261 261
262 262 When asList set to True, always return a list of widget(s) owning
263 263 the kernel. The list might be empty or containing several Widget.
264 264 """
265 265
266 266 #convert from/to int/richIpythonWidget if needed
267 267 if type(tab) == int:
268 268 tab = self.tab_widget.widget(tab)
269 269 km=tab.kernel_manager;
270 270
271 271 #build list of all widgets
272 272 widgetList = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
273 273
274 274 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
275 275 # And should have a _may_close attribute
276 276 filtredwidgetList = [ widget for widget in widgetList if
277 277 widget.kernel_manager.shell_address == km.shell_address and
278 278 widget.kernel_manager.sub_address == km.sub_address and
279 279 widget.kernel_manager.stdin_address == km.stdin_address and
280 280 widget.kernel_manager.hb_address == km.hb_address and
281 281 hasattr(widget,'_may_close') ]
282 282 # the master widget is the one that may close the kernel
283 283 masterWidget= [ widget for widget in filtredwidgetList if widget._may_close]
284 284 if asList:
285 285 return masterWidget
286 286 assert(len(masterWidget)<=1 )
287 287 if len(masterWidget)==0:
288 288 return None
289 289
290 290 return masterWidget[0]
291 291
292 292 def findSlavesTabs(self,tab):
293 293 """
294 294 Try to return all the frontend that do not own the kernel attached to the given widget/tab.
295 295
296 296 Only find frontend owed by the current application. Selection
297 297 based on port of the kernel, might be innacurate if several kernel
298 298 on different ip use same port number.
299 299
300 300 This fonction does the conversion tabNumber/widget if needed.
301 301 """
302 302 #convert from/to int/richIpythonWidget if needed
303 303 if type(tab) == int:
304 304 tab = self.tab_widget.widget(tab)
305 305 km=tab.kernel_manager;
306 306
307 307 #build list of all widgets
308 308 widgetList = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
309 309
310 310 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
311 311 filtredWidgetList = ( widget for widget in widgetList if
312 312 widget.kernel_manager.shell_address == km.shell_address and
313 313 widget.kernel_manager.sub_address == km.sub_address and
314 314 widget.kernel_manager.stdin_address == km.stdin_address and
315 315 widget.kernel_manager.hb_address == km.hb_address)
316 316 # Get a list of all widget owning the same kernel and removed it from
317 317 # the previous cadidate. (better using sets ?)
318 masterWidgetlist = self.findMasterTab(tab,asList=True)
318 masterWidgetlist = self.find_master_tab(tab,asList=True)
319 319 slaveList = [widget for widget in filtredWidgetList if widget not in masterWidgetlist]
320 320
321 321 return slaveList
322 322
323 323 # MenuBar is always present on Mac Os, so let's populate it with possible
324 324 # action, don't do it on other platform as some user might not want the
325 325 # menu bar, or give them an option to remove it
326 326 def initMenuBar(self):
327 327 #create menu in the order they should appear in the menu bar
328 328 self.fileMenu = self.menuBar().addMenu("&File")
329 329 self.editMenu = self.menuBar().addMenu("&Edit")
330 330 self.fontMenu = self.menuBar().addMenu("F&ont")
331 331 self.windowMenu = self.menuBar().addMenu("&Window")
332 332 self.magicMenu = self.menuBar().addMenu("&Magic")
333 333
334 334 # please keep the Help menu in Mac Os even if empty. It will
335 335 # automatically contain a search field to search inside menus and
336 336 # please keep it spelled in English, as long as Qt Doesn't support
337 337 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
338 338 # this search field fonctionnality
339 339
340 340 self.helpMenu = self.menuBar().addMenu("&Help")
341 341
342 342 # sould wrap every line of the following block into a try/except,
343 343 # as we are not sure of instanciating a _frontend which support all
344 344 # theses actions, but there might be a better way
345 345 try:
346 346 self.print_action = QtGui.QAction("&Print",
347 347 self,
348 348 shortcut="Ctrl+P",
349 349 triggered=self.print_action_active_frontend)
350 350 self.fileMenu.addAction(self.print_action)
351 351 except AttributeError:
352 352 self.log.error("trying to add unexisting action (print), skipping")
353 353
354 354 try:
355 355 self.export_action=QtGui.QAction("E&xport",
356 356 self,
357 357 shortcut="Ctrl+S",
358 358 triggered=self.export_action_active_frontend
359 359 )
360 360 self.fileMenu.addAction(self.export_action)
361 361 except AttributeError:
362 362 self.log.error("trying to add unexisting action (Export), skipping")
363 363
364 364 try:
365 365 self.select_all_action = QtGui.QAction("Select &All",
366 366 self,
367 367 shortcut="Ctrl+A",
368 368 triggered=self.select_all_active_frontend
369 369 )
370 370 self.fileMenu.addAction(self.select_all_action)
371 371 except AttributeError:
372 372 self.log.error("trying to add unexisting action (select all), skipping")
373 373
374 374 try:
375 375 self.undo_action = QtGui.QAction("&Undo",
376 376 self,
377 377 shortcut="Ctrl+Z",
378 378 statusTip="Undo last action if possible",
379 379 triggered=self.undo_active_frontend
380 380 )
381 381 self.editMenu.addAction(self.undo_action)
382 382 except AttributeError:
383 383 self.log.error("trying to add unexisting action (undo), skipping")
384 384
385 385 try:
386 386 self.redo_action = QtGui.QAction("&Redo",
387 387 self,
388 388 shortcut="Ctrl+Shift+Z",
389 389 statusTip="Redo last action if possible",
390 390 triggered=self.redo_active_frontend)
391 391 self.editMenu.addAction(self.redo_action)
392 392 except AttributeError:
393 393 self.log.error("trying to add unexisting action (redo), skipping")
394 394
395 395 try:
396 396 self.increase_font_size = QtGui.QAction("&Increase Font Size",
397 397 self,
398 398 shortcut="Ctrl++",
399 399 triggered=self.increase_font_size_active_frontend
400 400 )
401 401 self.fontMenu.addAction(self.increase_font_size)
402 402 except AttributeError:
403 403 self.log.error("trying to add unexisting action (increase font size), skipping")
404 404
405 405 try:
406 406 self.decrease_font_size = QtGui.QAction("&Decrease Font Size",
407 407 self,
408 408 shortcut="Ctrl+-",
409 409 triggered=self.decrease_font_size_active_frontend
410 410 )
411 411 self.fontMenu.addAction(self.decrease_font_size)
412 412 except AttributeError:
413 413 self.log.error("trying to add unexisting action (decrease font size), skipping")
414 414
415 415 try:
416 416 self.reset_font_size = QtGui.QAction("&Reset Font Size",
417 417 self,
418 418 shortcut="Ctrl+0",
419 419 triggered=self.reset_font_size_active_frontend
420 420 )
421 421 self.fontMenu.addAction(self.reset_font_size)
422 422 except AttributeError:
423 423 self.log.error("trying to add unexisting action (reset font size), skipping")
424 424
425 425 try:
426 426 self.reset_action = QtGui.QAction("&Reset",
427 427 self,
428 428 statusTip="Clear all varible from workspace",
429 429 triggered=self.reset_magic_active_frontend)
430 430 self.magicMenu.addAction(self.reset_action)
431 431 except AttributeError:
432 432 self.log.error("trying to add unexisting action (reset), skipping")
433 433
434 434 try:
435 435 self.history_action = QtGui.QAction("&History",
436 436 self,
437 437 statusTip="show command history",
438 438 triggered=self.history_magic_active_frontend)
439 439 self.magicMenu.addAction(self.history_action)
440 440 except AttributeError:
441 441 self.log.error("trying to add unexisting action (history), skipping")
442 442
443 443 try:
444 444 self.save_action = QtGui.QAction("E&xport History ",
445 445 self,
446 446 statusTip="Export History as Python File",
447 447 triggered=self.save_magic_active_frontend)
448 448 self.magicMenu.addAction(self.save_action)
449 449 except AttributeError:
450 450 self.log.error("trying to add unexisting action (save), skipping")
451 451
452 452 try:
453 453 self.clear_action = QtGui.QAction("&Clear",
454 454 self,
455 455 statusTip="Clear the console",
456 456 triggered=self.clear_magic_active_frontend)
457 457 self.magicMenu.addAction(self.clear_action)
458 458 except AttributeError:
459 459 self.log.error("trying to add unexisting action, skipping")
460 460
461 461 try:
462 462 self.who_action = QtGui.QAction("&Who",
463 463 self,
464 464 statusTip="List interactive variable",
465 465 triggered=self.who_magic_active_frontend)
466 466 self.magicMenu.addAction(self.who_action)
467 467 except AttributeError:
468 468 self.log.error("trying to add unexisting action (who), skipping")
469 469
470 470 try:
471 471 self.who_ls_action = QtGui.QAction("Wh&o ls",
472 472 self,
473 473 statusTip="Return a list of interactive variable",
474 474 triggered=self.who_ls_magic_active_frontend)
475 475 self.magicMenu.addAction(self.who_ls_action)
476 476 except AttributeError:
477 477 self.log.error("trying to add unexisting action (who_ls), skipping")
478 478
479 479 try:
480 480 self.whos_action = QtGui.QAction("Who&s",
481 481 self,
482 482 statusTip="List interactive variable with detail",
483 483 triggered=self.whos_magic_active_frontend)
484 484 self.magicMenu.addAction(self.whos_action)
485 485 except AttributeError:
486 486 self.log.error("trying to add unexisting action (whos), skipping")
487 487
488 488 def undo_active_frontend(self):
489 489 self.active_frontend.undo()
490 490
491 491 def redo_active_frontend(self):
492 492 self.active_frontend.redo()
493 493 def reset_magic_active_frontend(self):
494 494 self.active_frontend.execute("%reset")
495 495 def history_magic_active_frontend(self):
496 496 self.active_frontend.history_magic()
497 497 def save_magic_active_frontend(self):
498 498 self.active_frontend.save_magic()
499 499 def clear_magic_active_frontend(self):
500 500 self.active_frontend.execute("%clear")
501 501 def who_magic_active_frontend(self):
502 502 self.active_frontend.execute("%who")
503 503 def who_ls_magic_active_frontend(self):
504 504 self.active_frontend.execute("%who_ls")
505 505 def whos_magic_active_frontend(self):
506 506 self.active_frontend.execute("%whos")
507 507
508 508 def print_action_active_frontend(self):
509 509 self.active_frontend.print_action.trigger()
510 510
511 511 def export_action_active_frontend(self):
512 512 self.active_frontend.export_action.trigger()
513 513
514 514 def select_all_active_frontend(self):
515 515 self.active_frontend.select_all_action.trigger()
516 516
517 517 def increase_font_size_active_frontend(self):
518 518 self.active_frontend.increase_font_size.trigger()
519 519 def decrease_font_size_active_frontend(self):
520 520 self.active_frontend.decrease_font_size.trigger()
521 521 def reset_font_size_active_frontend(self):
522 522 self.active_frontend.reset_font_size.trigger()
523 523 #---------------------------------------------------------------------------
524 524 # QWidget interface
525 525 #---------------------------------------------------------------------------
526 526
527 527 def closeEvent(self, event):
528 528 """ Forward the close event to every tabs contained by the windows
529 529 """
530 530 # Do Not loop on the widget count as it change while closing
531 531 widgetList=[ self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
532 532 for widget in widgetList:
533 533 self.close_tab(widget)
534 534 event.accept()
535 535
536 536 #-----------------------------------------------------------------------------
537 537 # Aliases and Flags
538 538 #-----------------------------------------------------------------------------
539 539
540 540 flags = dict(ipkernel_flags)
541 541 qt_flags = {
542 542 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
543 543 "Connect to an existing kernel. If no argument specified, guess most recent"),
544 544 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
545 545 "Use a pure Python kernel instead of an IPython kernel."),
546 546 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
547 547 "Disable rich text support."),
548 548 }
549 549 qt_flags.update(boolean_flag(
550 550 'gui-completion', 'ConsoleWidget.gui_completion',
551 551 "use a GUI widget for tab completion",
552 552 "use plaintext output for completion"
553 553 ))
554 554 qt_flags.update(boolean_flag(
555 555 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
556 556 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
557 557 to force a direct exit without any confirmation.
558 558 """,
559 559 """Don't prompt the user when exiting. This will terminate the kernel
560 560 if it is owned by the frontend, and leave it alive if it is external.
561 561 """
562 562 ))
563 563 flags.update(qt_flags)
564 564
565 565 aliases = dict(ipkernel_aliases)
566 566
567 567 qt_aliases = dict(
568 568 hb = 'IPythonQtConsoleApp.hb_port',
569 569 shell = 'IPythonQtConsoleApp.shell_port',
570 570 iopub = 'IPythonQtConsoleApp.iopub_port',
571 571 stdin = 'IPythonQtConsoleApp.stdin_port',
572 572 ip = 'IPythonQtConsoleApp.ip',
573 573 existing = 'IPythonQtConsoleApp.existing',
574 574 f = 'IPythonQtConsoleApp.connection_file',
575 575
576 576 style = 'IPythonWidget.syntax_style',
577 577 stylesheet = 'IPythonQtConsoleApp.stylesheet',
578 578 colors = 'ZMQInteractiveShell.colors',
579 579
580 580 editor = 'IPythonWidget.editor',
581 581 paging = 'ConsoleWidget.paging',
582 582 ssh = 'IPythonQtConsoleApp.sshserver',
583 583 )
584 584 aliases.update(qt_aliases)
585 585
586 586
587 587 #-----------------------------------------------------------------------------
588 588 # IPythonQtConsole
589 589 #-----------------------------------------------------------------------------
590 590
591 591
592 592 class IPythonQtConsoleApp(BaseIPythonApplication):
593 593 name = 'ipython-qtconsole'
594 594 default_config_file_name='ipython_config.py'
595 595
596 596 description = """
597 597 The IPython QtConsole.
598 598
599 599 This launches a Console-style application using Qt. It is not a full
600 600 console, in that launched terminal subprocesses will not be able to accept
601 601 input.
602 602
603 603 The QtConsole supports various extra features beyond the Terminal IPython
604 604 shell, such as inline plotting with matplotlib, via:
605 605
606 606 ipython qtconsole --pylab=inline
607 607
608 608 as well as saving your session as HTML, and printing the output.
609 609
610 610 """
611 611 examples = _examples
612 612
613 613 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
614 614 flags = Dict(flags)
615 615 aliases = Dict(aliases)
616 616
617 617 kernel_argv = List(Unicode)
618 618
619 619 # create requested profiles by default, if they don't exist:
620 620 auto_create = CBool(True)
621 621 # connection info:
622 622 ip = Unicode(LOCALHOST, config=True,
623 623 help="""Set the kernel\'s IP address [default localhost].
624 624 If the IP address is something other than localhost, then
625 625 Consoles on other machines will be able to connect
626 626 to the Kernel, so be careful!"""
627 627 )
628 628
629 629 sshserver = Unicode('', config=True,
630 630 help="""The SSH server to use to connect to the kernel.""")
631 631 sshkey = Unicode('', config=True,
632 632 help="""Path to the ssh key to use for logging in to the ssh server.""")
633 633
634 634 hb_port = Int(0, config=True,
635 635 help="set the heartbeat port [default: random]")
636 636 shell_port = Int(0, config=True,
637 637 help="set the shell (XREP) port [default: random]")
638 638 iopub_port = Int(0, config=True,
639 639 help="set the iopub (PUB) port [default: random]")
640 640 stdin_port = Int(0, config=True,
641 641 help="set the stdin (XREQ) port [default: random]")
642 642 connection_file = Unicode('', config=True,
643 643 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
644 644
645 645 This file will contain the IP, ports, and authentication key needed to connect
646 646 clients to this kernel. By default, this file will be created in the security-dir
647 647 of the current profile, but can be specified by absolute path.
648 648 """)
649 649 def _connection_file_default(self):
650 650 return 'kernel-%i.json' % os.getpid()
651 651
652 652 existing = Unicode('', config=True,
653 653 help="""Connect to an already running kernel""")
654 654
655 655 stylesheet = Unicode('', config=True,
656 656 help="path to a custom CSS stylesheet")
657 657
658 658 pure = CBool(False, config=True,
659 659 help="Use a pure Python kernel instead of an IPython kernel.")
660 660 plain = CBool(False, config=True,
661 661 help="Use a plaintext widget instead of rich text (plain can't print/save).")
662 662
663 663 def _pure_changed(self, name, old, new):
664 664 kind = 'plain' if self.plain else 'rich'
665 665 self.config.ConsoleWidget.kind = kind
666 666 if self.pure:
667 667 self.widget_factory = FrontendWidget
668 668 elif self.plain:
669 669 self.widget_factory = IPythonWidget
670 670 else:
671 671 self.widget_factory = RichIPythonWidget
672 672
673 673 _plain_changed = _pure_changed
674 674
675 675 confirm_exit = CBool(True, config=True,
676 676 help="""
677 677 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
678 678 to force a direct exit without any confirmation.""",
679 679 )
680 680
681 681 # the factory for creating a widget
682 682 widget_factory = Any(RichIPythonWidget)
683 683
684 684 def parse_command_line(self, argv=None):
685 685 super(IPythonQtConsoleApp, self).parse_command_line(argv)
686 686 if argv is None:
687 687 argv = sys.argv[1:]
688 688
689 689 self.kernel_argv = list(argv) # copy
690 690 # kernel should inherit default config file from frontend
691 691 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
692 692 # Scrub frontend-specific flags
693 693 for a in argv:
694 694 if a.startswith('-') and a.lstrip('-') in qt_flags:
695 695 self.kernel_argv.remove(a)
696 696 swallow_next = False
697 697 for a in argv:
698 698 if swallow_next:
699 699 self.kernel_argv.remove(a)
700 700 swallow_next = False
701 701 continue
702 702 if a.startswith('-'):
703 703 split = a.lstrip('-').split('=')
704 704 alias = split[0]
705 705 if alias in qt_aliases:
706 706 self.kernel_argv.remove(a)
707 707 if len(split) == 1:
708 708 # alias passed with arg via space
709 709 swallow_next = True
710 710
711 711 def init_connection_file(self):
712 712 """find the connection file, and load the info if found.
713 713
714 714 The current working directory and the current profile's security
715 715 directory will be searched for the file if it is not given by
716 716 absolute path.
717 717
718 718 When attempting to connect to an existing kernel and the `--existing`
719 719 argument does not match an existing file, it will be interpreted as a
720 720 fileglob, and the matching file in the current profile's security dir
721 721 with the latest access time will be used.
722 722 """
723 723 if self.existing:
724 724 try:
725 725 cf = find_connection_file(self.existing)
726 726 except Exception:
727 727 self.log.critical("Could not find existing kernel connection file %s", self.existing)
728 728 self.exit(1)
729 729 self.log.info("Connecting to existing kernel: %s" % cf)
730 730 self.connection_file = cf
731 731 # should load_connection_file only be used for existing?
732 732 # as it is now, this allows reusing ports if an existing
733 733 # file is requested
734 734 try:
735 735 self.load_connection_file()
736 736 except Exception:
737 737 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
738 738 self.exit(1)
739 739
740 740 def load_connection_file(self):
741 741 """load ip/port/hmac config from JSON connection file"""
742 742 # this is identical to KernelApp.load_connection_file
743 743 # perhaps it can be centralized somewhere?
744 744 try:
745 745 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
746 746 except IOError:
747 747 self.log.debug("Connection File not found: %s", self.connection_file)
748 748 return
749 749 self.log.debug(u"Loading connection file %s", fname)
750 750 with open(fname) as f:
751 751 s = f.read()
752 752 cfg = json.loads(s)
753 753 if self.ip == LOCALHOST and 'ip' in cfg:
754 754 # not overridden by config or cl_args
755 755 self.ip = cfg['ip']
756 756 for channel in ('hb', 'shell', 'iopub', 'stdin'):
757 757 name = channel + '_port'
758 758 if getattr(self, name) == 0 and name in cfg:
759 759 # not overridden by config or cl_args
760 760 setattr(self, name, cfg[name])
761 761 if 'key' in cfg:
762 762 self.config.Session.key = str_to_bytes(cfg['key'])
763 763
764 764 def init_ssh(self):
765 765 """set up ssh tunnels, if needed."""
766 766 if not self.sshserver and not self.sshkey:
767 767 return
768 768
769 769 if self.sshkey and not self.sshserver:
770 770 # specifying just the key implies that we are connecting directly
771 771 self.sshserver = self.ip
772 772 self.ip = LOCALHOST
773 773
774 774 # build connection dict for tunnels:
775 775 info = dict(ip=self.ip,
776 776 shell_port=self.shell_port,
777 777 iopub_port=self.iopub_port,
778 778 stdin_port=self.stdin_port,
779 779 hb_port=self.hb_port
780 780 )
781 781
782 782 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
783 783
784 784 # tunnels return a new set of ports, which will be on localhost:
785 785 self.ip = LOCALHOST
786 786 try:
787 787 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
788 788 except:
789 789 # even catch KeyboardInterrupt
790 790 self.log.error("Could not setup tunnels", exc_info=True)
791 791 self.exit(1)
792 792
793 793 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
794 794
795 795 cf = self.connection_file
796 796 base,ext = os.path.splitext(cf)
797 797 base = os.path.basename(base)
798 798 self.connection_file = os.path.basename(base)+'-ssh'+ext
799 799 self.log.critical("To connect another client via this tunnel, use:")
800 800 self.log.critical("--existing %s" % self.connection_file)
801 801
802 802 def init_kernel_manager(self):
803 803 # Don't let Qt or ZMQ swallow KeyboardInterupts.
804 804 signal.signal(signal.SIGINT, signal.SIG_DFL)
805 805 sec = self.profile_dir.security_dir
806 806 try:
807 807 cf = filefind(self.connection_file, ['.', sec])
808 808 except IOError:
809 809 # file might not exist
810 810 if self.connection_file == os.path.basename(self.connection_file):
811 811 # just shortname, put it in security dir
812 812 cf = os.path.join(sec, self.connection_file)
813 813 else:
814 814 cf = self.connection_file
815 815
816 816 # Create a KernelManager and start a kernel.
817 817 self.kernel_manager = QtKernelManager(
818 818 ip=self.ip,
819 819 shell_port=self.shell_port,
820 820 iopub_port=self.iopub_port,
821 821 stdin_port=self.stdin_port,
822 822 hb_port=self.hb_port,
823 823 connection_file=cf,
824 824 config=self.config,
825 825 )
826 826 # start the kernel
827 827 if not self.existing:
828 828 kwargs = dict(ipython=not self.pure)
829 829 kwargs['extra_arguments'] = self.kernel_argv
830 830 self.kernel_manager.start_kernel(**kwargs)
831 831 elif self.sshserver:
832 832 # ssh, write new connection file
833 833 self.kernel_manager.write_connection_file()
834 834 self.kernel_manager.start_channels()
835 835
836 def createTabWithNewFrontend(self):
836 def create_tab_with_new_frontend(self):
837 837 """ Create new tab attached to new kernel, launched on localhost.
838 838 """
839 839 kernel_manager = QtKernelManager(
840 840 shell_address=(LOCALHOST,0 ),
841 841 sub_address=(LOCALHOST, 0),
842 842 stdin_address=(LOCALHOST, 0),
843 843 hb_address=(LOCALHOST, 0),
844 844 config=self.config
845 845 )
846 846 # start the kernel
847 847 kwargs = dict(ip=LOCALHOST, ipython=not self.pure)
848 848 kwargs['extra_arguments'] = self.kernel_argv
849 849 kernel_manager.start_kernel(**kwargs)
850 850 kernel_manager.start_channels()
851 851 local_kernel = (not False) or self.ip in LOCAL_IPS
852 852 widget = self.widget_factory(config=self.config,
853 853 local_kernel=local_kernel)
854 854 widget.kernel_manager = kernel_manager
855 855 widget._existing=False;
856 856 widget._confirm_exit=True;
857 857 widget._may_close=True;
858 self.window.addTabWithFrontend(widget)
858 self.window.add_tab_with_frontend(widget)
859 859
860 def createTabAttachedToCurrentTabKernel(self):
860 def create_tab_attached_to_current_tab_kernel(self):
861 861 currentWidget = self.window.tab_widget.currentWidget()
862 862 currentWidgetIndex = self.window.tab_widget.indexOf(currentWidget)
863 863 currentWidget.kernel_manager = currentWidget.kernel_manager;
864 864 currentWidgetName = self.window.tab_widget.tabText(currentWidgetIndex);
865 865 kernel_manager = QtKernelManager(
866 866 shell_address = currentWidget.kernel_manager.shell_address,
867 867 sub_address = currentWidget.kernel_manager.sub_address,
868 868 stdin_address = currentWidget.kernel_manager.stdin_address,
869 869 hb_address = currentWidget.kernel_manager.hb_address,
870 870 config = self.config
871 871 )
872 872 kernel_manager.start_channels()
873 873 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
874 874 widget = self.widget_factory(config=self.config,
875 875 local_kernel=False)
876 876 widget._confirm_exit=True;
877 877 widget._may_close=False;
878 878 widget.kernel_manager = kernel_manager
879 self.window.addTabWithFrontend(widget,name=str('('+currentWidgetName+') slave'))
879 self.window.add_tab_with_frontend(widget,name=str('('+currentWidgetName+') slave'))
880 880
881 881 def init_qt_elements(self):
882 882 # Create the widget.
883 883 self.app = QtGui.QApplication([])
884 884 pixmap=QtGui.QPixmap(':/icon/IPythonConsole.png')
885 885 icon=QtGui.QIcon(pixmap)
886 886 QtGui.QApplication.setWindowIcon(icon)
887 887
888 888 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
889 889 self.widget = self.widget_factory(config=self.config,
890 890 local_kernel=local_kernel)
891 891 self.widget._existing = self.existing;
892 892 self.widget._may_close = not self.existing;
893 893 self.widget._confirm_exit = not self.existing;
894 894
895 895 self.widget.kernel_manager = self.kernel_manager
896 896 self.window = MainWindow(self.app, self.widget, self.existing,
897 897 may_close=local_kernel,
898 898 confirm_exit=self.confirm_exit)
899 899 self.window.log = self.log
900 self.window.addTabWithFrontend(self.widget)
900 self.window.add_tab_with_frontend(self.widget)
901 901 self.window.initMenuBar()
902 902 self.window.setWindowTitle('Python' if self.pure else 'IPython')
903 903
904 904 def init_colors(self):
905 905 """Configure the coloring of the widget"""
906 906 # Note: This will be dramatically simplified when colors
907 907 # are removed from the backend.
908 908
909 909 if self.pure:
910 910 # only IPythonWidget supports styling
911 911 return
912 912
913 913 # parse the colors arg down to current known labels
914 914 try:
915 915 colors = self.config.ZMQInteractiveShell.colors
916 916 except AttributeError:
917 917 colors = None
918 918 try:
919 919 style = self.config.IPythonWidget.colors
920 920 except AttributeError:
921 921 style = None
922 922
923 923 # find the value for colors:
924 924 if colors:
925 925 colors=colors.lower()
926 926 if colors in ('lightbg', 'light'):
927 927 colors='lightbg'
928 928 elif colors in ('dark', 'linux'):
929 929 colors='linux'
930 930 else:
931 931 colors='nocolor'
932 932 elif style:
933 933 if style=='bw':
934 934 colors='nocolor'
935 935 elif styles.dark_style(style):
936 936 colors='linux'
937 937 else:
938 938 colors='lightbg'
939 939 else:
940 940 colors=None
941 941
942 942 # Configure the style.
943 943 widget = self.widget
944 944 if style:
945 945 widget.style_sheet = styles.sheet_from_template(style, colors)
946 946 widget.syntax_style = style
947 947 widget._syntax_style_changed()
948 948 widget._style_sheet_changed()
949 949 elif colors:
950 950 # use a default style
951 951 widget.set_default_style(colors=colors)
952 952 else:
953 953 # this is redundant for now, but allows the widget's
954 954 # defaults to change
955 955 widget.set_default_style()
956 956
957 957 if self.stylesheet:
958 958 # we got an expicit stylesheet
959 959 if os.path.isfile(self.stylesheet):
960 960 with open(self.stylesheet) as f:
961 961 sheet = f.read()
962 962 widget.style_sheet = sheet
963 963 widget._style_sheet_changed()
964 964 else:
965 965 raise IOError("Stylesheet %r not found."%self.stylesheet)
966 966
967 967 def initialize(self, argv=None):
968 968 super(IPythonQtConsoleApp, self).initialize(argv)
969 969 self.init_connection_file()
970 970 default_secure(self.config)
971 971 self.init_ssh()
972 972 self.init_kernel_manager()
973 973 self.init_qt_elements()
974 974 self.init_colors()
975 975 self.init_window_shortcut()
976 976
977 977 def init_window_shortcut(self):
978 978
979 979 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
980 980 self.window,
981 981 shortcut="Ctrl+PgDown",
982 982 statusTip="Cahange to next tab",
983 983 triggered=self.window.prev_tab)
984 984
985 985 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
986 986 self.window,
987 987 shortcut="Ctrl+PgUp",
988 988 statusTip="Cahange to next tab",
989 989 triggered=self.window.next_tab)
990 990
991 991 self.fullScreenAct = QtGui.QAction("&Full Screen",
992 992 self.window,
993 993 shortcut="Ctrl+Meta+Space",
994 994 statusTip="Toggle between Fullscreen and Normal Size",
995 995 triggered=self.toggleFullScreen)
996 996
997 997 self.tabAndNewKernelAct =QtGui.QAction("Tab with &New kernel",
998 998 self.window,
999 999 shortcut="Ctrl+T",
1000 triggered=self.createTabWithNewFrontend)
1000 triggered=self.create_tab_with_new_frontend)
1001 1001 self.window.windowMenu.addAction(self.tabAndNewKernelAct)
1002 1002 self.tabSameKernalAct =QtGui.QAction("Tab with Sa&me kernel",
1003 1003 self.window,
1004 1004 shortcut="Ctrl+Shift+T",
1005 triggered=self.createTabAttachedToCurrentTabKernel)
1005 triggered=self.create_tab_attached_to_current_tab_kernel)
1006 1006 self.window.windowMenu.addAction(self.tabSameKernalAct)
1007 1007 self.window.windowMenu.addSeparator()
1008 1008
1009 1009 # creating shortcut in menubar only for Mac OS as I don't
1010 1010 # know the shortcut or if the windows manager assign it in
1011 1011 # other platform.
1012 1012 if sys.platform == 'darwin':
1013 1013 self.minimizeAct = QtGui.QAction("Mini&mize",
1014 1014 self.window,
1015 1015 shortcut="Ctrl+m",
1016 1016 statusTip="Minimize the window/Restore Normal Size",
1017 1017 triggered=self.toggleMinimized)
1018 1018 self.maximizeAct = QtGui.QAction("Ma&ximize",
1019 1019 self.window,
1020 1020 shortcut="Ctrl+Shift+M",
1021 1021 statusTip="Maximize the window/Restore Normal Size",
1022 1022 triggered=self.toggleMaximized)
1023 1023
1024 1024 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
1025 1025 self.window,
1026 1026 triggered=self._open_online_help)
1027 1027
1028 1028 self.windowMenu = self.window.windowMenu
1029 1029
1030 1030 self.windowMenu.addAction(self.next_tab_act)
1031 1031 self.windowMenu.addAction(self.prev_tab_act)
1032 1032 self.windowMenu.addSeparator()
1033 1033 self.windowMenu.addAction(self.minimizeAct)
1034 1034 self.windowMenu.addAction(self.maximizeAct)
1035 1035 self.windowMenu.addSeparator()
1036 1036 self.windowMenu.addAction(self.fullScreenAct)
1037 1037
1038 1038 self.window.helpMenu.addAction(self.onlineHelpAct)
1039 1039 else:
1040 1040 # if we don't put it in a menu, we add it to the window so
1041 1041 # that it can still be triggerd by shortcut
1042 1042 self.window.addAction(self.fullScreenAct)
1043 1043
1044 1044 def toggleMinimized(self):
1045 1045 if not self.window.isMinimized():
1046 1046 self.window.showMinimized()
1047 1047 else:
1048 1048 self.window.showNormal()
1049 1049
1050 1050 def _open_online_help(self):
1051 1051 QtGui.QDesktopServices.openUrl(
1052 1052 QtCore.QUrl("http://ipython.org/documentation.html",
1053 1053 QtCore.QUrl.TolerantMode)
1054 1054 )
1055 1055
1056 1056 def toggleMaximized(self):
1057 1057 if not self.window.isMaximized():
1058 1058 self.window.showMaximized()
1059 1059 else:
1060 1060 self.window.showNormal()
1061 1061
1062 1062 # Min/Max imizing while in full screen give a bug
1063 1063 # when going out of full screen, at least on OSX
1064 1064 def toggleFullScreen(self):
1065 1065 if not self.window.isFullScreen():
1066 1066 self.window.showFullScreen()
1067 1067 if sys.platform == 'darwin':
1068 1068 self.maximizeAct.setEnabled(False)
1069 1069 self.minimizeAct.setEnabled(False)
1070 1070 else:
1071 1071 self.window.showNormal()
1072 1072 if sys.platform == 'darwin':
1073 1073 self.maximizeAct.setEnabled(True)
1074 1074 self.minimizeAct.setEnabled(True)
1075 1075
1076 1076 def start(self):
1077 1077
1078 1078 # draw the window
1079 1079 self.window.show()
1080 1080
1081 1081 # Start the application main loop.
1082 1082 self.app.exec_()
1083 1083
1084 1084 #-----------------------------------------------------------------------------
1085 1085 # Main entry point
1086 1086 #-----------------------------------------------------------------------------
1087 1087
1088 1088 def main():
1089 1089 app = IPythonQtConsoleApp()
1090 1090 app.initialize()
1091 1091 app.start()
1092 1092
1093 1093
1094 1094 if __name__ == '__main__':
1095 1095 main()
General Comments 0
You need to be logged in to leave comments. Login now