diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index 79b5754..87919c1 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -25,33 +25,13 @@ from IPython.core.inputsplitter import IPythonInputSplitter, \ from IPython.core.usage import default_gui_banner from IPython.utils.traitlets import Bool, Str from frontend_widget import FrontendWidget +from styles import (default_light_style_sheet, default_dark_style_sheet, + default_light_syntax_style, default_dark_syntax_style) #----------------------------------------------------------------------------- # Constants #----------------------------------------------------------------------------- -# The default light style sheet: black text on a white background. -default_light_style_sheet = ''' - .error { color: red; } - .in-prompt { color: navy; } - .in-prompt-number { font-weight: bold; } - .out-prompt { color: darkred; } - .out-prompt-number { font-weight: bold; } -''' -default_light_syntax_style = 'default' - -# The default dark style sheet: white text on a black background. -default_dark_style_sheet = ''' - QPlainTextEdit, QTextEdit { background-color: black; color: white } - QFrame { border: 1px solid grey; } - .error { color: red; } - .in-prompt { color: lime; } - .in-prompt-number { color: lime; font-weight: bold; } - .out-prompt { color: red; } - .out-prompt-number { color: red; font-weight: bold; } -''' -default_dark_syntax_style = 'monokai' - # Default strings to build and display input and output prompts (and separators # in between) default_in_prompt = 'In [%i]: ' diff --git a/IPython/frontend/qt/console/ipythonqt.py b/IPython/frontend/qt/console/ipythonqt.py index 74e7641..21e7b65 100644 --- a/IPython/frontend/qt/console/ipythonqt.py +++ b/IPython/frontend/qt/console/ipythonqt.py @@ -7,12 +7,13 @@ # Systemm library imports from PyQt4 import QtGui - +from pygments.styles import get_all_styles # Local imports from IPython.external.argparse import ArgumentParser 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 #----------------------------------------------------------------------------- @@ -129,7 +130,7 @@ def main(): 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]') + help='set the heartbeat port [default random]') egroup = kgroup.add_mutually_exclusive_group() egroup.add_argument('--pure', action='store_true', help = \ @@ -148,6 +149,14 @@ def main(): help='enable 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, + help='specify a pygments style by name. \ + Valid are: %s'%(list(get_all_styles()))) + wgroup.add_argument('--stylesheet', type=str, + help="path to a custom CSS stylesheet.") + wgroup.add_argument('--dark', action='store_true', + help="use the dark style template instead of lightbg.\ + If --style is not specified, the default dark style is used.") args = parser.parse_args() @@ -186,6 +195,33 @@ def main(): 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: + # guess whether it's a dark style: + dark = args.dark or styles.dark_style(args.style) + widget.syntax_style = args.style + widget.style_sheet = styles.sheet_from_template(args.style, not dark) + widget._syntax_style_changed() + widget._style_sheet_changed() + elif args.dark: + # use default dark style + widget.set_default_style(lightbg=False) + else: + # this is redundant for now, but allows the widget's + # defaults to change + widget.set_default_style(lightbg=True) + + if args.stylesheet: + # we got an expicit stylesheet + if os.path.isfile(args.stylesheet): + with open(args.stylesheet) as f: + sheet = f.read() + widget.style_sheet = sheet + widget._style_sheet_changed() + else: + raise IOError("Stylesheet %r not found."%args.stylesheet) + # Create the main window. window = MainWindow(app, widget, args.existing, may_close=local_kernel) window.setWindowTitle('Python' if args.pure else 'IPython') diff --git a/IPython/frontend/qt/console/styles.py b/IPython/frontend/qt/console/styles.py new file mode 100644 index 0000000..11055c2 --- /dev/null +++ b/IPython/frontend/qt/console/styles.py @@ -0,0 +1,102 @@ +""" Style utilities, templates, and defaults for syntax highlighting widgets. +""" +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from colorsys import rgb_to_hls +from pygments.styles import get_style_by_name +from pygments.token import Token + +#----------------------------------------------------------------------------- +# Constants +#----------------------------------------------------------------------------- + +# The default light style sheet: black text on a white background. +default_light_style_template = ''' + QPlainTextEdit, QTextEdit { background-color: %(bgcolor)s; + color: %(fgcolor)s ; + selection-background-color: %(select)s} + .error { color: red; } + .in-prompt { color: navy; } + .in-prompt-number { font-weight: bold; } + .out-prompt { color: darkred; } + .out-prompt-number { font-weight: bold; } +''' +default_light_style_sheet = default_light_style_template%dict( + bgcolor='white', fgcolor='black', select="#ccc") +default_light_syntax_style = 'default' + +# The default dark style sheet: white text on a black background. +default_dark_style_template = ''' + QPlainTextEdit, QTextEdit { background-color: %(bgcolor)s; + color: %(fgcolor)s ; + selection-background-color: %(select)s} + QFrame { border: 1px solid grey; } + .error { color: red; } + .in-prompt { color: lime; } + .in-prompt-number { color: lime; font-weight: bold; } + .out-prompt { color: red; } + .out-prompt-number { color: red; font-weight: bold; } +''' +default_dark_style_sheet = default_dark_style_template%dict( + bgcolor='black', fgcolor='white', select="#555") +default_dark_syntax_style = 'monokai' + +def hex_to_rgb(color): + """Convert a hex color to rgb integer tuple.""" + if color.startswith('#'): + color = color[1:] + if len(color) == 3: + color = ''.join([c*2 for c in color]) + if len(color) != 6: + return False + try: + r = int(color[:2],16) + g = int(color[:2],16) + b = int(color[:2],16) + except ValueError: + return False + else: + return r,g,b + +def dark_color(color): + """Check whether a color is 'dark'. + + Currently, this is simply whether the luminance is <50%""" + rgb = hex_to_rgb(color) + if rgb: + return rgb_to_hls(*rgb)[1] < 128 + else: # default to False + return False + +def dark_style(stylename): + """Guess whether the background of the style with name 'stylename' + counts as 'dark'.""" + return dark_color(get_style_by_name(stylename).background_color) + +def get_colors(stylename): + """Construct the keys to be used building the base stylesheet.""" + style = get_style_by_name(stylename) + fgcolor = style.style_for_token(Token.Text)['color'] or '' + if len(fgcolor) in (3,6): + # could be 'abcdef' or 'ace' hex, which needs '#' prefix + try: + int(fgcolor, 16) + except TypeError: + pass + else: + fgcolor = "#"+fgcolor + + return dict( + bgcolor = style.background_color, + select = style.highlight_color, + fgcolor = fgcolor + ) + +def sheet_from_template(name, lightbg=True): + """Use one of the base templates, and set bg/fg/select colors.""" + if lightbg: + return default_light_style_template%get_colors(name) + else: + return default_dark_style_template%get_colors(name) \ No newline at end of file