1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 """Module to parse ANSI escape sequences
7 Maintainer: Jean-Paul Calderone
13 from twisted.python import log
17 Represents an element of text along with the texts colors and
18 additional attributes.
22 COLORS = ('b', 'r', 'g', 'y', 'l', 'm', 'c', 'w')
23 BOLD_COLORS = tuple([x.upper() for x in COLORS])
24 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(len(COLORS))
28 'Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White'
31 def __init__(self, text, fg, bg, display, bold, underline, flash, reverse):
32 self.text, self.fg, self.bg = text, fg, bg
33 self.display = display
35 self.underline = underline
37 self.reverse = reverse
39 self.fg, self.bg = self.bg, self.fg
44 Parser class for ANSI codes.
47 # Terminators for cursor movement ansi controls - unsupported
48 CURSOR_SET = ('H', 'f', 'A', 'B', 'C', 'D', 'R', 's', 'u', 'd','G')
50 # Terminators for erasure ansi controls - unsupported
51 ERASE_SET = ('J', 'K', 'P')
53 # Terminators for mode change ansi controls - unsupported
56 # Terminators for keyboard assignment ansi controls - unsupported
59 # Terminators for color change ansi controls - supported
62 SETS = (CURSOR_SET, ERASE_SET, MODE_SET, ASSIGN_SET, COLOR_SET)
64 def __init__(self, defaultFG, defaultBG):
65 self.defaultFG, self.defaultBG = defaultFG, defaultBG
66 self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
67 self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
72 def stripEscapes(self, string):
74 Remove all ANSI color escapes from the given string.
81 if show == 0 and string[i] in _sets:
84 n = string.find('\x1B', i)
86 return result + string[i:]
88 result = result + string[i:n]
94 def writeString(self, colorstr):
97 def parseString(self, str):
99 Turn a string input into a list of L{ColorText} elements.
103 str = self.prepend + str
105 parts = str.split('\x1B')
108 self.writeString(self.formatText(parts[0]))
110 self.writeString(self.formatText(parts[0]))
116 if s[i] not in string.digits+'[;?':
120 self.prepend = '\x1b'
123 self.writeString(self.formatText(s[i+1:]))
129 self.prepend = '\x1b['
131 type = _setmap.get(s[i], None)
135 if type == AnsiParser.COLOR_SET:
136 self.parseColor(s[:i + 1])
138 self.writeString(self.formatText(s))
139 elif type == AnsiParser.CURSOR_SET:
140 cursor, s = s[:i+1], s[i+1:]
141 self.parseCursor(cursor)
142 self.writeString(self.formatText(s))
143 elif type == AnsiParser.ERASE_SET:
144 erase, s = s[:i+1], s[i+1:]
145 self.parseErase(erase)
146 self.writeString(self.formatText(s))
147 elif type == AnsiParser.MODE_SET:
148 mode, s = s[:i+1], s[i+1:]
149 #self.parseErase('2J')
150 self.writeString(self.formatText(s))
152 self.prepend = '\x1B[' + s
154 log.msg('Unhandled ANSI control type: %c' % (s[i],))
156 self.writeString(self.formatText(s))
158 def parseColor(self, str):
160 Handle a single ANSI color sequence
162 # Drop the trailing 'm'
169 parts = map(int, str.split(';'))
171 log.msg('Invalid ANSI color sequence (%d): %s' % (len(str), str))
172 self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
177 self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
178 self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
183 self.currentFG = x - 30
185 self.currentBG = x - 40
187 self.currentFG = self.defaultFG
189 self.currentBG = self.defaultBG
209 log.msg('Unrecognised ANSI color command: %d' % (x,))
211 def parseCursor(self, cursor):
214 def parseErase(self, erase):
218 def pickColor(self, value, mode, BOLD = ColorText.BOLD_COLORS):
220 return ColorText.COLORS[value]
222 return self.bold and BOLD[value] or ColorText.COLORS[value]
225 def formatText(self, text):
228 self.pickColor(self.currentFG, 0),
229 self.pickColor(self.currentBG, 1),
230 self.display, self.bold, self.underline, self.flash, self.reverse
234 _sets = ''.join(map(''.join, AnsiParser.SETS))
237 for s in AnsiParser.SETS: