diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 1d8cff0..2147254 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -55,33 +55,39 @@ class ConsoleWidget(Configurable, QtGui.QWidget): #------ Configuration ------------------------------------------------------ - # Whether to process ANSI escape codes. - ansi_codes = Bool(True, config=True) - - # The maximum number of lines of text before truncation. Specifying a - # non-positive number disables text truncation (not recommended). - buffer_size = Int(500, config=True) - - # Whether to use a list widget or plain text output for tab completion. - gui_completion = Bool(False, config=True) - - # The type of underlying text widget to use. Valid values are 'plain', which - # specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit. + ansi_codes = Bool(True, config=True, + help="Whether to process ANSI escape codes." + ) + buffer_size = Int(500, config=True, + help=""" + The maximum number of lines of text before truncation. Specifying a + non-positive number disables text truncation (not recommended). + """ + ) + gui_completion = Bool(False, config=True, + help="Use a list widget instead of plain text output for tab completion." + ) # NOTE: this value can only be specified during initialization. - kind = Enum(['plain', 'rich'], default_value='plain', config=True) - - # The type of paging to use. Valid values are: - # 'inside' : The widget pages like a traditional terminal. - # 'hsplit' : When paging is requested, the widget is split - # horizontally. The top pane contains the console, and the - # bottom pane contains the paged text. - # 'vsplit' : Similar to 'hsplit', except that a vertical splitter used. - # 'custom' : No action is taken by the widget beyond emitting a - # 'custom_page_requested(str)' signal. - # 'none' : The text is written directly to the console. + kind = Enum(['plain', 'rich'], default_value='plain', config=True, + help=""" + The type of underlying text widget to use. Valid values are 'plain', which + specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit. + """ + ) # NOTE: this value can only be specified during initialization. paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'], - default_value='inside', config=True) + default_value='inside', config=True, + help=""" + The type of paging to use. Valid values are: + 'inside' : The widget pages like a traditional terminal. + 'hsplit' : When paging is requested, the widget is split + : horizontally. The top pane contains the console, and the + : bottom pane contains the paged text. + 'vsplit' : Similar to 'hsplit', except that a vertical splitter used. + 'custom' : No action is taken by the widget beyond emitting a + : 'custom_page_requested(str)' signal. + 'none' : The text is written directly to the console. + """) # Whether to override ShortcutEvents for the keybindings defined by this # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 9f699a5..966864b 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -13,7 +13,7 @@ from IPython.external.qt import QtCore, QtGui from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt from IPython.core.oinspect import call_tip from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin -from IPython.utils.traitlets import Bool +from IPython.utils.traitlets import Bool, Instance from bracket_matcher import BracketMatcher from call_tip_widget import CallTipWidget from completion_lexer import CompletionLexer @@ -106,6 +106,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind']) _input_splitter_class = InputSplitter _local_kernel = False + _highlighter = Instance(FrontendHighlighter) #--------------------------------------------------------------------------- # 'object' interface diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index 50f6b3a..f04efd7 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -23,9 +23,7 @@ from IPython.core.inputsplitter import IPythonInputSplitter, \ from IPython.core.usage import default_gui_banner from IPython.utils.traitlets import Bool, Str, Unicode from frontend_widget import FrontendWidget -from styles import (default_light_style_sheet, default_light_syntax_style, - default_dark_style_sheet, default_dark_syntax_style, - default_bw_style_sheet, default_bw_syntax_style) +import styles #----------------------------------------------------------------------------- # Constants @@ -56,26 +54,35 @@ class IPythonWidget(FrontendWidget): custom_edit = Bool(False) custom_edit_requested = QtCore.Signal(object, object) - # A command for invoking a system text editor. If the string contains a - # {filename} format specifier, it will be used. Otherwise, the filename will - # be appended to the end the command. - editor = Unicode('default', config=True) - - # The editor command to use when a specific line number is requested. The - # string should contain two format specifiers: {line} and {filename}. If - # this parameter is not specified, the line number option to the %edit magic - # will be ignored. - editor_line = Unicode(config=True) - - # A CSS stylesheet. The stylesheet can contain classes for: - # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc - # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter) - # 3. IPython: .error, .in-prompt, .out-prompt, etc - style_sheet = Unicode(config=True) + editor = Unicode('default', config=True, + help=""" + A command for invoking a system text editor. If the string contains a + {filename} format specifier, it will be used. Otherwise, the filename will + be appended to the end the command. + """) + + editor_line = Unicode(config=True, + help=""" + The editor command to use when a specific line number is requested. The + string should contain two format specifiers: {line} and {filename}. If + this parameter is not specified, the line number option to the %edit magic + will be ignored. + """) + + style_sheet = Unicode(config=True, + help=""" + A CSS stylesheet. The stylesheet can contain classes for: + 1. Qt: QPlainTextEdit, QFrame, QWidget, etc + 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter) + 3. IPython: .error, .in-prompt, .out-prompt, etc + """) - # If not empty, use this Pygments style for syntax highlighting. Otherwise, - # the style sheet is queried for Pygments style information. - syntax_style = Str(config=True) + + syntax_style = Str(config=True, + help=""" + If not empty, use this Pygments style for syntax highlighting. Otherwise, + the style sheet is queried for Pygments style information. + """) # Prompts. in_prompt = Str(default_in_prompt, config=True) @@ -371,14 +378,14 @@ class IPythonWidget(FrontendWidget): """ colors = colors.lower() if colors=='lightbg': - self.style_sheet = default_light_style_sheet - self.syntax_style = default_light_syntax_style + self.style_sheet = styles.default_light_style_sheet + self.syntax_style = styles.default_light_syntax_style elif colors=='linux': - self.style_sheet = default_dark_style_sheet - self.syntax_style = default_dark_syntax_style + self.style_sheet = styles.default_dark_style_sheet + self.syntax_style = styles.default_dark_syntax_style elif colors=='nocolor': - self.style_sheet = default_bw_style_sheet - self.syntax_style = default_bw_syntax_style + self.style_sheet = styles.default_bw_style_sheet + self.syntax_style = styles.default_bw_syntax_style else: raise KeyError("No such color scheme: %s"%colors) @@ -482,9 +489,13 @@ class IPythonWidget(FrontendWidget): bg_color = self._control.palette().window().color() self._ansi_processor.set_background_color(bg_color) + def _syntax_style_changed(self): """ Set the style for the syntax highlighter. """ + if self._highlighter is None: + # ignore premature calls + return if self.syntax_style: self._highlighter.set_style(self.syntax_style) else: diff --git a/IPython/frontend/qt/console/ipythonqt.py b/IPython/frontend/qt/console/ipythonqt.py index 03034e2..5fc787b 100644 --- a/IPython/frontend/qt/console/ipythonqt.py +++ b/IPython/frontend/qt/console/ipythonqt.py @@ -5,17 +5,32 @@ # Imports #----------------------------------------------------------------------------- -# Systemm library imports +# stdlib imports +import os +import signal +import sys + +# System library imports from IPython.external.qt import QtGui from pygments.styles import get_all_styles # Local imports -from IPython.external.argparse import ArgumentParser +from IPython.core.newapplication import ProfileDir, BaseIPythonApplication from IPython.frontend.qt.console.frontend_widget import FrontendWidget from IPython.frontend.qt.console.ipython_widget import IPythonWidget from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget from IPython.frontend.qt.console import styles from IPython.frontend.qt.kernelmanager import QtKernelManager +from IPython.utils.traitlets import ( + Dict, List, Unicode, Int, CaselessStrEnum, Bool, Any +) +from IPython.zmq.ipkernel import ( + flags as ipkernel_flags, + aliases as ipkernel_aliases, + IPKernelApp +) +from IPython.zmq.zmqshell import ZMQInteractiveShell + #----------------------------------------------------------------------------- # Network Constants @@ -127,126 +142,188 @@ class MainWindow(QtGui.QMainWindow): event.accept() #----------------------------------------------------------------------------- -# Main entry point +# Aliases and Flags #----------------------------------------------------------------------------- -def main(): - """ Entry point for application. - """ - # Parse command line arguments. - parser = ArgumentParser() - kgroup = parser.add_argument_group('kernel options') - kgroup.add_argument('-e', '--existing', action='store_true', - help='connect to an existing kernel') - kgroup.add_argument('--ip', type=str, default=LOCALHOST, - help=\ - "set the kernel\'s IP address [default localhost].\ - If the IP address is something other than localhost, then \ - Consoles on other machines will be able to connect\ - to the Kernel, so be careful!") - kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0, - help='set the XREQ channel port [default random]') - kgroup.add_argument('--sub', type=int, metavar='PORT', default=0, - help='set the SUB channel port [default random]') - kgroup.add_argument('--rep', type=int, metavar='PORT', default=0, - help='set the REP channel port [default random]') - kgroup.add_argument('--hb', type=int, metavar='PORT', default=0, - help='set the heartbeat port [default random]') - - egroup = kgroup.add_mutually_exclusive_group() - egroup.add_argument('--pure', action='store_true', help = \ - 'use a pure Python kernel instead of an IPython kernel') - egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?', - const='auto', help = \ - "Pre-load matplotlib and numpy for interactive use. If GUI is not \ - given, the GUI backend is matplotlib's, otherwise use one of: \ - ['tk', 'gtk', 'qt', 'wx', 'inline'].") - - wgroup = parser.add_argument_group('widget options') - wgroup.add_argument('--paging', type=str, default='inside', - choices = ['inside', 'hsplit', 'vsplit', 'none'], - help='set the paging style [default inside]') - wgroup.add_argument('--plain', action='store_true', - help='disable rich text support') - wgroup.add_argument('--gui-completion', action='store_true', - help='use a GUI widget for tab completion') - wgroup.add_argument('--style', type=str, - choices = list(get_all_styles()), - help='specify a pygments style for by name') - wgroup.add_argument('--stylesheet', type=str, - help='path to a custom CSS stylesheet') - wgroup.add_argument('--colors', type=str, help = \ - "Set the color scheme (LightBG,Linux,NoColor). This is guessed \ - based on the pygments style if not set.") - - args = parser.parse_args() - - # parse the colors arg down to current known labels - if args.colors: - colors=args.colors.lower() - if colors in ('lightbg', 'light'): - colors='lightbg' - elif colors in ('dark', 'linux'): - colors='linux' - else: - colors='nocolor' - elif args.style: - if args.style=='bw': - colors='nocolor' - elif styles.dark_style(args.style): - colors='linux' +flags = dict(ipkernel_flags) + +flags.update({ + 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}}, + "Connect to an existing kernel."), + 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}}, + "Use a pure Python kernel instead of an IPython kernel."), + 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}}, + "Disable rich text support."), + 'gui-completion' : ({'FrontendWidget' : {'gui_completion' : True}}, + "use a GUI widget for tab completion"), +}) + +qt_flags = ['existing', 'pure', 'plain', 'gui-completion'] + +aliases = dict(ipkernel_aliases) + +aliases.update(dict( + hb = 'IPythonQtConsoleApp.hb_port', + shell = 'IPythonQtConsoleApp.shell_port', + iopub = 'IPythonQtConsoleApp.iopub_port', + stdin = 'IPythonQtConsoleApp.stdin_port', + ip = 'IPythonQtConsoleApp.ip', + + plain = 'IPythonQtConsoleApp.plain', + pure = 'IPythonQtConsoleApp.pure', + gui_completion = 'FrontendWidget.gui_completion', + style = 'IPythonWidget.syntax_style', + stylesheet = 'IPythonQtConsoleApp.stylesheet', + colors = 'ZMQInteractiveShell.colors', + + editor = 'IPythonWidget.editor', + pi = 'IPythonWidget.in_prompt', + po = 'IPythonWidget.out_prompt', + si = 'IPythonWidget.input_sep', + so = 'IPythonWidget.output_sep', + so2 = 'IPythonWidget.output_sep2', +)) + +#----------------------------------------------------------------------------- +# IPythonQtConsole +#----------------------------------------------------------------------------- + +class IPythonQtConsoleApp(BaseIPythonApplication): + name = 'ipython-qtconsole' + default_config_file_name='ipython_config.py' + classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir] + flags = Dict(flags) + aliases = Dict(aliases) + + kernel_argv = List(Unicode) + + # connection info: + ip = Unicode(LOCALHOST, config=True, + help="""Set the kernel\'s IP address [default localhost]. + If the IP address is something other than localhost, then + Consoles on other machines will be able to connect + to the Kernel, so be careful!""" + ) + hb_port = Int(0, config=True, + help="set the heartbeat port [default: random]") + shell_port = Int(0, config=True, + help="set the shell (XREP) port [default: random]") + iopub_port = Int(0, config=True, + help="set the iopub (PUB) port [default: random]") + stdin_port = Int(0, config=True, + help="set the stdin (XREQ) port [default: random]") + + existing = Bool(False, config=True, + help="Whether to connect to an already running Kernel.") + + stylesheet = Unicode('', config=True, + help="path to a custom CSS stylesheet") + + pure = Bool(False, config=True, + help="Use a pure Python kernel instead of an IPython kernel.") + plain = Bool(False, config=True, + help="Use a pure Python kernel instead of an IPython kernel.") + + def _pure_changed(self, name, old, new): + kind = 'plain' if self.plain else 'rich' + self.config.ConsoleWidget.kind = kind + if self.pure: + self.widget_factory = FrontendWidget + elif self.plain: + self.widget_factory = IPythonWidget else: - colors='lightbg' - else: - colors=None - - # Don't let Qt or ZMQ swallow KeyboardInterupts. - import signal - signal.signal(signal.SIGINT, signal.SIG_DFL) - - # Create a KernelManager and start a kernel. - kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq), - sub_address=(args.ip, args.sub), - rep_address=(args.ip, args.rep), - hb_address=(args.ip, args.hb)) - if not args.existing: - # if not args.ip in LOCAL_IPS+ALL_ALIAS: - # raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS) - - kwargs = dict(ip=args.ip) - if args.pure: - kwargs['ipython']=False + self.widget_factory = RichIPythonWidget + + _plain_changed = _pure_changed + + # the factory for creating a widget + widget_factory = Any(RichIPythonWidget) + + def parse_command_line(self, argv=None): + super(IPythonQtConsoleApp, self).parse_command_line(argv) + if argv is None: + argv = sys.argv[1:] + + self.kernel_argv = list(argv) # copy + + # scrub frontend-specific flags + for a in argv: + if a.startswith('--') and a[2:] in qt_flags: + self.kernel_argv.remove(a) + + def init_kernel_manager(self): + # Don't let Qt or ZMQ swallow KeyboardInterupts. + signal.signal(signal.SIGINT, signal.SIG_DFL) + + # Create a KernelManager and start a kernel. + self.kernel_manager = QtKernelManager( + xreq_address=(self.ip, self.shell_port), + sub_address=(self.ip, self.iopub_port), + rep_address=(self.ip, self.stdin_port), + hb_address=(self.ip, self.hb_port) + ) + # start the kernel + if not self.existing: + kwargs = dict(ip=self.ip, ipython=not self.pure) + kwargs['extra_arguments'] = self.kernel_argv + self.kernel_manager.start_kernel(**kwargs) + self.kernel_manager.start_channels() + + + def init_qt_elements(self): + # Create the widget. + self.app = QtGui.QApplication([]) + local_kernel = (not self.existing) or self.ip in LOCAL_IPS + self.widget = self.widget_factory(config=self.config, + local_kernel=local_kernel) + self.widget.kernel_manager = self.kernel_manager + self.window = MainWindow(self.app, self.widget, self.existing, + may_close=local_kernel) + self.window.setWindowTitle('Python' if self.pure else 'IPython') + + def init_colors(self): + """Configure the coloring of the widget""" + # Note: This will be dramatically simplified when colors + # are removed from the backend. + + if self.pure: + # only IPythonWidget supports styling + return + + # parse the colors arg down to current known labels + try: + colors = self.config.ZMQInteractiveShell.colors + except AttributeError: + colors = None + try: + style = self.config.IPythonWidget.colors + except AttributeError: + style = None + + # find the value for colors: + if colors: + colors=colors.lower() + if colors in ('lightbg', 'light'): + colors='lightbg' + elif colors in ('dark', 'linux'): + colors='linux' + else: + colors='nocolor' + elif style: + if style=='bw': + colors='nocolor' + elif styles.dark_style(style): + colors='linux' + else: + colors='lightbg' else: - extra = [] - if colors: - extra.append("colors=%s"%colors) - if args.pylab: - extra.append("pylab=%s"%args.pylab) - kwargs['extra_arguments'] = extra - - kernel_manager.start_kernel(**kwargs) - kernel_manager.start_channels() - - # Create the widget. - app = QtGui.QApplication([]) - local_kernel = (not args.existing) or args.ip in LOCAL_IPS - if args.pure: - kind = 'plain' if args.plain else 'rich' - widget = FrontendWidget(kind=kind, paging=args.paging, - local_kernel=local_kernel) - elif args.plain: - widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel) - else: - widget = RichIPythonWidget(paging=args.paging, - local_kernel=local_kernel) - widget.gui_completion = args.gui_completion - widget.kernel_manager = kernel_manager - - # Configure the style. - if not args.pure: # only IPythonWidget supports styles - if args.style: - widget.syntax_style = args.style - widget.style_sheet = styles.sheet_from_template(args.style, colors) + colors=None + + # Configure the style. + widget = self.widget + if style: + widget.style_sheet = styles.sheet_from_template(style, colors) + widget.syntax_style = style widget._syntax_style_changed() widget._style_sheet_changed() elif colors: @@ -257,23 +334,38 @@ def main(): # defaults to change widget.set_default_style() - if args.stylesheet: + if self.stylesheet: # we got an expicit stylesheet - if os.path.isfile(args.stylesheet): - with open(args.stylesheet) as f: + if os.path.isfile(self.stylesheet): + with open(self.stylesheet) as f: sheet = f.read() widget.style_sheet = sheet widget._style_sheet_changed() else: - raise IOError("Stylesheet %r not found."%args.stylesheet) + raise IOError("Stylesheet %r not found."%self.stylesheet) + + def initialize(self, argv=None): + super(IPythonQtConsoleApp, self).initialize(argv) + self.init_kernel_manager() + self.init_qt_elements() + self.init_colors() + + def start(self): + + # draw the window + self.window.show() - # Create the main window. - window = MainWindow(app, widget, args.existing, may_close=local_kernel) - window.setWindowTitle('Python' if args.pure else 'IPython') - window.show() + # Start the application main loop. + self.app.exec_() - # Start the application main loop. - app.exec_() +#----------------------------------------------------------------------------- +# Main entry point +#----------------------------------------------------------------------------- + +def main(): + app = IPythonQtConsoleApp() + app.initialize() + app.start() if __name__ == '__main__':