Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'minrk-pygments' into trunk

Provide full support for all the Pygments styles at the console, and
properly synchronize with IPython's own Linux/LightBG color schemes.
In the long run we need to refactor our internal coloring code to only
use CSS, but for now this provides a fairly clean user experience.

Closes gh-171 (pull request)
  • Loading branch information...
commit e6c4eeed7e0f08e6b3b76fcee0f62a3d9e1ee99a 2 parents 5c6a347 + d7934bc
Min RK minrk authored fperez committed
41 IPython/frontend/qt/console/ipython_widget.py
View
@@ -25,33 +25,14 @@
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_light_syntax_style,
+ default_dark_style_sheet, default_dark_syntax_style,
+ default_bw_style_sheet, default_bw_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 [<span class="in-prompt-number">%i</span>]: '
@@ -349,21 +330,27 @@ def _show_interpreter_prompt_for_reply(self, msg):
# 'IPythonWidget' interface
#---------------------------------------------------------------------------
- def set_default_style(self, lightbg=True):
+ def set_default_style(self, colors='lightbg'):
""" Sets the widget style to the class defaults.
Parameters:
-----------
- lightbg : bool, optional (default True)
+ colors : str, optional (default lightbg)
Whether to use the default IPython light background or dark
- background style.
+ background or B&W style.
"""
- if lightbg:
+ colors = colors.lower()
+ if colors=='lightbg':
self.style_sheet = default_light_style_sheet
self.syntax_style = default_light_syntax_style
- else:
+ elif colors=='linux':
self.style_sheet = default_dark_style_sheet
self.syntax_style = default_dark_syntax_style
+ elif colors=='nocolor':
+ self.style_sheet = default_bw_style_sheet
+ self.syntax_style = default_bw_syntax_style
+ else:
+ raise KeyError("No such color scheme: %s"%colors)
#---------------------------------------------------------------------------
# 'IPythonWidget' protected interface
67 IPython/frontend/qt/console/ipythonqt.py
View
@@ -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,9 +149,36 @@ 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,
+ 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'
+ else:
+ colors='lightbg'
+ else:
+ colors=None
+
# Don't let Qt or ZMQ swallow KeyboardInterupts.
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
@@ -163,13 +191,15 @@ def main():
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
- elif args.pylab:
- kwargs['pylab']=args.pylab
-
+ else:
+ kwargs['colors']=colors
+ if args.pylab:
+ kwargs['pylab']=args.pylab
+
kernel_manager.start_kernel(**kwargs)
kernel_manager.start_channels()
@@ -186,6 +216,31 @@ 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:
+ widget.syntax_style = args.style
+ widget.style_sheet = styles.sheet_from_template(args.style, colors)
+ widget._syntax_style_changed()
+ widget._style_sheet_changed()
+ elif colors:
+ # use a default style
+ widget.set_default_style(colors=colors)
+ else:
+ # this is redundant for now, but allows the widget's
+ # defaults to change
+ widget.set_default_style()
+
+ 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')
119 IPython/frontend/qt/console/styles.py
View
@@ -0,0 +1,119 @@
+""" 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'
+
+# The default monochrome
+default_bw_style_sheet = '''
+ QPlainTextEdit, QTextEdit { background-color: white;
+ color: black ;
+ selection-background-color: #cccccc}
+ .in-prompt-number { font-weight: bold; }
+ .out-prompt-number { font-weight: bold; }
+'''
+default_bw_syntax_style = 'bw'
+
+
+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
+ from a templatee."""
+ 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, colors='lightbg'):
+ """Use one of the base templates, and set bg/fg/select colors."""
+ colors = colors.lower()
+ if colors=='lightbg':
+ return default_light_style_template%get_colors(name)
+ elif colors=='linux':
+ return default_dark_style_template%get_colors(name)
+ elif colors=='nocolor':
+ return default_bw_style_sheet
+ else:
+ raise KeyError("No such color scheme: %s"%colors)
14 IPython/zmq/ipkernel.py
View
@@ -535,7 +535,7 @@ def start(self):
#-----------------------------------------------------------------------------
def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
- independent=False, pylab=False):
+ independent=False, pylab=False, colors=None):
"""Launches a localhost kernel, binding to the specified ports.
Parameters
@@ -566,6 +566,9 @@ def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
string is passed, matplotlib will use the specified backend. Otherwise,
matplotlib's default backend will be used.
+ colors : None or string, optional (default None)
+ If not None, specify the color scheme. One of (NoColor, LightBG, Linux)
+
Returns
-------
A tuple of form:
@@ -581,6 +584,9 @@ def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
extra_arguments.append('--ip')
if isinstance(ip, basestring):
extra_arguments.append(ip)
+ if colors is not None:
+ extra_arguments.append('--colors')
+ extra_arguments.append(colors)
return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
xrep_port, pub_port, req_port, hb_port,
independent, extra_arguments)
@@ -595,6 +601,10 @@ def main():
"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'].")
+ parser.add_argument('--colors',
+ type=str, dest='colors',
+ help="Set the color scheme (NoColor, Linux, and LightBG).",
+ metavar='ZMQInteractiveShell.colors')
namespace = parser.parse_args()
kernel_class = Kernel
@@ -616,6 +626,8 @@ def main():
if kernel_class is None:
raise ValueError('GUI is not supported: %r' % gui)
pylabtools.activate_matplotlib(backend)
+ if namespace.colors:
+ ZMQInteractiveShell.colors=namespace.colors
kernel = make_kernel(namespace, kernel_class, OutStream)
Please sign in to comment.
Something went wrong with that request. Please try again.