##// END OF EJS Templates
First cut at support readline-esque history filtering in ConsoleWidget.
First cut at support readline-esque history filtering in ConsoleWidget.

File last commit:

r2870:aa8566a6
r2984:3395782b
Show More
ansi_code_processor.py
203 lines | 6.6 KiB | text/x-python | PythonLexer
# Standard library imports
import re
# System library imports
from PyQt4 import QtCore, QtGui
class AnsiAction(object):
""" Represents an action requested by an ANSI escape sequence.
"""
def __init__(self, kind):
self.kind = kind
class MoveAction(AnsiAction):
""" An AnsiAction for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL,
CHA, and CUP commands).
"""
def __init__(self):
raise NotImplementedError
class EraseAction(AnsiAction):
""" An AnsiAction for erase requests (ED and EL commands).
"""
def __init__(self, area, erase_to):
super(EraseAction, self).__init__('erase')
self.area = area
self.erase_to = erase_to
class AnsiCodeProcessor(object):
""" Translates ANSI escape codes into readable attributes.
"""
# Whether to increase intensity or set boldness for SGR code 1.
# (Different terminals handle this in different ways.)
bold_text_enabled = False
# Protected class variables.
_ansi_commands = 'ABCDEFGHJKSTfmnsu'
_ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands)
def __init__(self):
self.actions = []
self.reset_sgr()
def reset_sgr(self):
""" Reset graphics attributs to their default values.
"""
self.intensity = 0
self.italic = False
self.bold = False
self.underline = False
self.foreground_color = None
self.background_color = None
def split_string(self, string):
""" Yields substrings for which the same escape code applies.
"""
self.actions = []
start = 0
for match in self._ansi_pattern.finditer(string):
substring = string[start:match.start()]
if substring or self.actions:
yield substring
start = match.end()
self.actions = []
try:
params = []
for param in match.group(1).split(';'):
if param:
params.append(int(param))
except ValueError:
# Silently discard badly formed escape codes.
pass
else:
self.set_csi_code(match.group(2), params)
substring = string[start:]
if substring or self.actions:
yield substring
def set_csi_code(self, command, params=[]):
""" Set attributes based on CSI (Control Sequence Introducer) code.
Parameters
----------
command : str
The code identifier, i.e. the final character in the sequence.
params : sequence of integers, optional
The parameter codes for the command.
"""
if command == 'm': # SGR - Select Graphic Rendition
for code in params:
self.set_sgr_code(code)
elif (command == 'J' or # ED - Erase Data
command == 'K'): # EL - Erase in Line
code = params[0] if params else 0
if 0 <= code <= 2:
area = 'screen' if command == 'J' else 'line'
if code == 0:
erase_to = 'end'
elif code == 1:
erase_to = 'start'
elif code == 2:
erase_to = 'all'
self.actions.append(EraseAction(area, erase_to))
def set_sgr_code(self, code):
""" Set attributes based on SGR (Select Graphic Rendition) code.
"""
if code == 0:
self.reset_sgr()
elif code == 1:
if self.bold_text_enabled:
self.bold = True
else:
self.intensity = 1
elif code == 2:
self.intensity = 0
elif code == 3:
self.italic = True
elif code == 4:
self.underline = True
elif code == 22:
self.intensity = 0
self.bold = False
elif code == 23:
self.italic = False
elif code == 24:
self.underline = False
elif code >= 30 and code <= 37:
self.foreground_color = code - 30
elif code == 39:
self.foreground_color = None
elif code >= 40 and code <= 47:
self.background_color = code - 40
elif code == 49:
self.background_color = None
class QtAnsiCodeProcessor(AnsiCodeProcessor):
""" Translates ANSI escape codes into QTextCharFormats.
"""
# A map from color codes to RGB colors.
default_map = (# Normal, Bright/Light ANSI color code
('black', 'grey'), # 0: black
('darkred', 'red'), # 1: red
('darkgreen', 'lime'), # 2: green
('brown', 'yellow'), # 3: yellow
('darkblue', 'deepskyblue'), # 4: blue
('darkviolet', 'magenta'), # 5: magenta
('steelblue', 'cyan'), # 6: cyan
('grey', 'white')) # 7: white
def __init__(self):
super(QtAnsiCodeProcessor, self).__init__()
self.color_map = self.default_map
def get_format(self):
""" Returns a QTextCharFormat that encodes the current style attributes.
"""
format = QtGui.QTextCharFormat()
# Set foreground color
if self.foreground_color is not None:
color = self.color_map[self.foreground_color][self.intensity]
format.setForeground(QtGui.QColor(color))
# Set background color
if self.background_color is not None:
color = self.color_map[self.background_color][self.intensity]
format.setBackground(QtGui.QColor(color))
# Set font weight/style options
if self.bold:
format.setFontWeight(QtGui.QFont.Bold)
else:
format.setFontWeight(QtGui.QFont.Normal)
format.setFontItalic(self.italic)
format.setFontUnderline(self.underline)
return format
def set_background_color(self, color):
""" Given a background color (a QColor), attempt to set a color map
that will be aesthetically pleasing.
"""
if color.value() < 127:
# Colors appropriate for a terminal with a dark background.
self.color_map = self.default_map
else:
# Colors appropriate for a terminal with a light background. For
# now, only use non-bright colors...
self.color_map = [ (pair[0], pair[0]) for pair in self.default_map ]
# ...and replace white with black.
self.color_map[7] = ('black', 'black')