##// END OF EJS Templates
[qtconsole] carriage-return action matches CR only, not CRLF...
MinRK -
Show More
@@ -1,348 +1,348 b''
1 """ Utilities for processing ANSI escape codes and special ASCII characters.
1 """ Utilities for processing ANSI escape codes and special ASCII characters.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 # Standard library imports
7 # Standard library imports
8 from collections import namedtuple
8 from collections import namedtuple
9 import re
9 import re
10
10
11 # System library imports
11 # System library imports
12 from IPython.external.qt import QtCore, QtGui
12 from IPython.external.qt import QtCore, QtGui
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Constants and datatypes
15 # Constants and datatypes
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # An action for erase requests (ED and EL commands).
18 # An action for erase requests (ED and EL commands).
19 EraseAction = namedtuple('EraseAction', ['action', 'area', 'erase_to'])
19 EraseAction = namedtuple('EraseAction', ['action', 'area', 'erase_to'])
20
20
21 # An action for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL, CHA, CUP,
21 # An action for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL, CHA, CUP,
22 # and HVP commands).
22 # and HVP commands).
23 # FIXME: Not implemented in AnsiCodeProcessor.
23 # FIXME: Not implemented in AnsiCodeProcessor.
24 MoveAction = namedtuple('MoveAction', ['action', 'dir', 'unit', 'count'])
24 MoveAction = namedtuple('MoveAction', ['action', 'dir', 'unit', 'count'])
25
25
26 # An action for scroll requests (SU and ST) and form feeds.
26 # An action for scroll requests (SU and ST) and form feeds.
27 ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count'])
27 ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count'])
28
28
29 # An action for the carriage return character
29 # An action for the carriage return character
30 CarriageReturnAction = namedtuple('CarriageReturnAction', ['action'])
30 CarriageReturnAction = namedtuple('CarriageReturnAction', ['action'])
31
31
32 # An action for the beep character
32 # An action for the beep character
33 BeepAction = namedtuple('BeepAction', ['action'])
33 BeepAction = namedtuple('BeepAction', ['action'])
34
34
35 # Regular expressions.
35 # Regular expressions.
36 CSI_COMMANDS = 'ABCDEFGHJKSTfmnsu'
36 CSI_COMMANDS = 'ABCDEFGHJKSTfmnsu'
37 CSI_SUBPATTERN = '\[(.*?)([%s])' % CSI_COMMANDS
37 CSI_SUBPATTERN = '\[(.*?)([%s])' % CSI_COMMANDS
38 OSC_SUBPATTERN = '\](.*?)[\x07\x1b]'
38 OSC_SUBPATTERN = '\](.*?)[\x07\x1b]'
39 ANSI_PATTERN = ('\x01?\x1b(%s|%s)\x02?' % \
39 ANSI_PATTERN = ('\x01?\x1b(%s|%s)\x02?' % \
40 (CSI_SUBPATTERN, OSC_SUBPATTERN))
40 (CSI_SUBPATTERN, OSC_SUBPATTERN))
41 ANSI_OR_SPECIAL_PATTERN = re.compile('(\b|\r)|(?:%s)' % ANSI_PATTERN)
41 ANSI_OR_SPECIAL_PATTERN = re.compile('(\b|\r(?!\n))|(?:%s)' % ANSI_PATTERN)
42 SPECIAL_PATTERN = re.compile('([\f])')
42 SPECIAL_PATTERN = re.compile('([\f])')
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Classes
45 # Classes
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 class AnsiCodeProcessor(object):
48 class AnsiCodeProcessor(object):
49 """ Translates special ASCII characters and ANSI escape codes into readable
49 """ Translates special ASCII characters and ANSI escape codes into readable
50 attributes. It also supports a few non-standard, xterm-specific codes.
50 attributes. It also supports a few non-standard, xterm-specific codes.
51 """
51 """
52
52
53 # Whether to increase intensity or set boldness for SGR code 1.
53 # Whether to increase intensity or set boldness for SGR code 1.
54 # (Different terminals handle this in different ways.)
54 # (Different terminals handle this in different ways.)
55 bold_text_enabled = False
55 bold_text_enabled = False
56
56
57 # We provide an empty default color map because subclasses will likely want
57 # We provide an empty default color map because subclasses will likely want
58 # to use a custom color format.
58 # to use a custom color format.
59 default_color_map = {}
59 default_color_map = {}
60
60
61 #---------------------------------------------------------------------------
61 #---------------------------------------------------------------------------
62 # AnsiCodeProcessor interface
62 # AnsiCodeProcessor interface
63 #---------------------------------------------------------------------------
63 #---------------------------------------------------------------------------
64
64
65 def __init__(self):
65 def __init__(self):
66 self.actions = []
66 self.actions = []
67 self.color_map = self.default_color_map.copy()
67 self.color_map = self.default_color_map.copy()
68 self.reset_sgr()
68 self.reset_sgr()
69
69
70 def reset_sgr(self):
70 def reset_sgr(self):
71 """ Reset graphics attributs to their default values.
71 """ Reset graphics attributs to their default values.
72 """
72 """
73 self.intensity = 0
73 self.intensity = 0
74 self.italic = False
74 self.italic = False
75 self.bold = False
75 self.bold = False
76 self.underline = False
76 self.underline = False
77 self.foreground_color = None
77 self.foreground_color = None
78 self.background_color = None
78 self.background_color = None
79
79
80 def split_string(self, string):
80 def split_string(self, string):
81 """ Yields substrings for which the same escape code applies.
81 """ Yields substrings for which the same escape code applies.
82 """
82 """
83 self.actions = []
83 self.actions = []
84 start = 0
84 start = 0
85
85
86 for match in ANSI_OR_SPECIAL_PATTERN.finditer(string):
86 for match in ANSI_OR_SPECIAL_PATTERN.finditer(string):
87 raw = string[start:match.start()]
87 raw = string[start:match.start()]
88 substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
88 substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
89 if substring or self.actions:
89 if substring or self.actions:
90 yield substring
90 yield substring
91 start = match.end()
91 start = match.end()
92
92
93 self.actions = []
93 self.actions = []
94 groups = filter(lambda x: x is not None, match.groups())
94 groups = filter(lambda x: x is not None, match.groups())
95 if groups[0] == '\r':
95 if groups[0] == '\r':
96 self.actions.append(CarriageReturnAction('carriage-return'))
96 self.actions.append(CarriageReturnAction('carriage-return'))
97 yield ''
97 yield ''
98 elif groups[0] == '\b':
98 elif groups[0] == '\b':
99 self.actions.append(BeepAction('beep'))
99 self.actions.append(BeepAction('beep'))
100 yield ''
100 yield ''
101 else:
101 else:
102 params = [ param for param in groups[1].split(';') if param ]
102 params = [ param for param in groups[1].split(';') if param ]
103 if groups[0].startswith('['):
103 if groups[0].startswith('['):
104 # Case 1: CSI code.
104 # Case 1: CSI code.
105 try:
105 try:
106 params = map(int, params)
106 params = map(int, params)
107 except ValueError:
107 except ValueError:
108 # Silently discard badly formed codes.
108 # Silently discard badly formed codes.
109 pass
109 pass
110 else:
110 else:
111 self.set_csi_code(groups[2], params)
111 self.set_csi_code(groups[2], params)
112
112
113 elif groups[0].startswith(']'):
113 elif groups[0].startswith(']'):
114 # Case 2: OSC code.
114 # Case 2: OSC code.
115 self.set_osc_code(params)
115 self.set_osc_code(params)
116
116
117 raw = string[start:]
117 raw = string[start:]
118 substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
118 substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
119 if substring or self.actions:
119 if substring or self.actions:
120 yield substring
120 yield substring
121
121
122 def set_csi_code(self, command, params=[]):
122 def set_csi_code(self, command, params=[]):
123 """ Set attributes based on CSI (Control Sequence Introducer) code.
123 """ Set attributes based on CSI (Control Sequence Introducer) code.
124
124
125 Parameters
125 Parameters
126 ----------
126 ----------
127 command : str
127 command : str
128 The code identifier, i.e. the final character in the sequence.
128 The code identifier, i.e. the final character in the sequence.
129
129
130 params : sequence of integers, optional
130 params : sequence of integers, optional
131 The parameter codes for the command.
131 The parameter codes for the command.
132 """
132 """
133 if command == 'm': # SGR - Select Graphic Rendition
133 if command == 'm': # SGR - Select Graphic Rendition
134 if params:
134 if params:
135 self.set_sgr_code(params)
135 self.set_sgr_code(params)
136 else:
136 else:
137 self.set_sgr_code([0])
137 self.set_sgr_code([0])
138
138
139 elif (command == 'J' or # ED - Erase Data
139 elif (command == 'J' or # ED - Erase Data
140 command == 'K'): # EL - Erase in Line
140 command == 'K'): # EL - Erase in Line
141 code = params[0] if params else 0
141 code = params[0] if params else 0
142 if 0 <= code <= 2:
142 if 0 <= code <= 2:
143 area = 'screen' if command == 'J' else 'line'
143 area = 'screen' if command == 'J' else 'line'
144 if code == 0:
144 if code == 0:
145 erase_to = 'end'
145 erase_to = 'end'
146 elif code == 1:
146 elif code == 1:
147 erase_to = 'start'
147 erase_to = 'start'
148 elif code == 2:
148 elif code == 2:
149 erase_to = 'all'
149 erase_to = 'all'
150 self.actions.append(EraseAction('erase', area, erase_to))
150 self.actions.append(EraseAction('erase', area, erase_to))
151
151
152 elif (command == 'S' or # SU - Scroll Up
152 elif (command == 'S' or # SU - Scroll Up
153 command == 'T'): # SD - Scroll Down
153 command == 'T'): # SD - Scroll Down
154 dir = 'up' if command == 'S' else 'down'
154 dir = 'up' if command == 'S' else 'down'
155 count = params[0] if params else 1
155 count = params[0] if params else 1
156 self.actions.append(ScrollAction('scroll', dir, 'line', count))
156 self.actions.append(ScrollAction('scroll', dir, 'line', count))
157
157
158 def set_osc_code(self, params):
158 def set_osc_code(self, params):
159 """ Set attributes based on OSC (Operating System Command) parameters.
159 """ Set attributes based on OSC (Operating System Command) parameters.
160
160
161 Parameters
161 Parameters
162 ----------
162 ----------
163 params : sequence of str
163 params : sequence of str
164 The parameters for the command.
164 The parameters for the command.
165 """
165 """
166 try:
166 try:
167 command = int(params.pop(0))
167 command = int(params.pop(0))
168 except (IndexError, ValueError):
168 except (IndexError, ValueError):
169 return
169 return
170
170
171 if command == 4:
171 if command == 4:
172 # xterm-specific: set color number to color spec.
172 # xterm-specific: set color number to color spec.
173 try:
173 try:
174 color = int(params.pop(0))
174 color = int(params.pop(0))
175 spec = params.pop(0)
175 spec = params.pop(0)
176 self.color_map[color] = self._parse_xterm_color_spec(spec)
176 self.color_map[color] = self._parse_xterm_color_spec(spec)
177 except (IndexError, ValueError):
177 except (IndexError, ValueError):
178 pass
178 pass
179
179
180 def set_sgr_code(self, params):
180 def set_sgr_code(self, params):
181 """ Set attributes based on SGR (Select Graphic Rendition) codes.
181 """ Set attributes based on SGR (Select Graphic Rendition) codes.
182
182
183 Parameters
183 Parameters
184 ----------
184 ----------
185 params : sequence of ints
185 params : sequence of ints
186 A list of SGR codes for one or more SGR commands. Usually this
186 A list of SGR codes for one or more SGR commands. Usually this
187 sequence will have one element per command, although certain
187 sequence will have one element per command, although certain
188 xterm-specific commands requires multiple elements.
188 xterm-specific commands requires multiple elements.
189 """
189 """
190 # Always consume the first parameter.
190 # Always consume the first parameter.
191 if not params:
191 if not params:
192 return
192 return
193 code = params.pop(0)
193 code = params.pop(0)
194
194
195 if code == 0:
195 if code == 0:
196 self.reset_sgr()
196 self.reset_sgr()
197 elif code == 1:
197 elif code == 1:
198 if self.bold_text_enabled:
198 if self.bold_text_enabled:
199 self.bold = True
199 self.bold = True
200 else:
200 else:
201 self.intensity = 1
201 self.intensity = 1
202 elif code == 2:
202 elif code == 2:
203 self.intensity = 0
203 self.intensity = 0
204 elif code == 3:
204 elif code == 3:
205 self.italic = True
205 self.italic = True
206 elif code == 4:
206 elif code == 4:
207 self.underline = True
207 self.underline = True
208 elif code == 22:
208 elif code == 22:
209 self.intensity = 0
209 self.intensity = 0
210 self.bold = False
210 self.bold = False
211 elif code == 23:
211 elif code == 23:
212 self.italic = False
212 self.italic = False
213 elif code == 24:
213 elif code == 24:
214 self.underline = False
214 self.underline = False
215 elif code >= 30 and code <= 37:
215 elif code >= 30 and code <= 37:
216 self.foreground_color = code - 30
216 self.foreground_color = code - 30
217 elif code == 38 and params and params.pop(0) == 5:
217 elif code == 38 and params and params.pop(0) == 5:
218 # xterm-specific: 256 color support.
218 # xterm-specific: 256 color support.
219 if params:
219 if params:
220 self.foreground_color = params.pop(0)
220 self.foreground_color = params.pop(0)
221 elif code == 39:
221 elif code == 39:
222 self.foreground_color = None
222 self.foreground_color = None
223 elif code >= 40 and code <= 47:
223 elif code >= 40 and code <= 47:
224 self.background_color = code - 40
224 self.background_color = code - 40
225 elif code == 48 and params and params.pop(0) == 5:
225 elif code == 48 and params and params.pop(0) == 5:
226 # xterm-specific: 256 color support.
226 # xterm-specific: 256 color support.
227 if params:
227 if params:
228 self.background_color = params.pop(0)
228 self.background_color = params.pop(0)
229 elif code == 49:
229 elif code == 49:
230 self.background_color = None
230 self.background_color = None
231
231
232 # Recurse with unconsumed parameters.
232 # Recurse with unconsumed parameters.
233 self.set_sgr_code(params)
233 self.set_sgr_code(params)
234
234
235 #---------------------------------------------------------------------------
235 #---------------------------------------------------------------------------
236 # Protected interface
236 # Protected interface
237 #---------------------------------------------------------------------------
237 #---------------------------------------------------------------------------
238
238
239 def _parse_xterm_color_spec(self, spec):
239 def _parse_xterm_color_spec(self, spec):
240 if spec.startswith('rgb:'):
240 if spec.startswith('rgb:'):
241 return tuple(map(lambda x: int(x, 16), spec[4:].split('/')))
241 return tuple(map(lambda x: int(x, 16), spec[4:].split('/')))
242 elif spec.startswith('rgbi:'):
242 elif spec.startswith('rgbi:'):
243 return tuple(map(lambda x: int(float(x) * 255),
243 return tuple(map(lambda x: int(float(x) * 255),
244 spec[5:].split('/')))
244 spec[5:].split('/')))
245 elif spec == '?':
245 elif spec == '?':
246 raise ValueError('Unsupported xterm color spec')
246 raise ValueError('Unsupported xterm color spec')
247 return spec
247 return spec
248
248
249 def _replace_special(self, match):
249 def _replace_special(self, match):
250 special = match.group(1)
250 special = match.group(1)
251 if special == '\f':
251 if special == '\f':
252 self.actions.append(ScrollAction('scroll', 'down', 'page', 1))
252 self.actions.append(ScrollAction('scroll', 'down', 'page', 1))
253 return ''
253 return ''
254
254
255
255
256 class QtAnsiCodeProcessor(AnsiCodeProcessor):
256 class QtAnsiCodeProcessor(AnsiCodeProcessor):
257 """ Translates ANSI escape codes into QTextCharFormats.
257 """ Translates ANSI escape codes into QTextCharFormats.
258 """
258 """
259
259
260 # A map from ANSI color codes to SVG color names or RGB(A) tuples.
260 # A map from ANSI color codes to SVG color names or RGB(A) tuples.
261 darkbg_color_map = {
261 darkbg_color_map = {
262 0 : 'black', # black
262 0 : 'black', # black
263 1 : 'darkred', # red
263 1 : 'darkred', # red
264 2 : 'darkgreen', # green
264 2 : 'darkgreen', # green
265 3 : 'brown', # yellow
265 3 : 'brown', # yellow
266 4 : 'darkblue', # blue
266 4 : 'darkblue', # blue
267 5 : 'darkviolet', # magenta
267 5 : 'darkviolet', # magenta
268 6 : 'steelblue', # cyan
268 6 : 'steelblue', # cyan
269 7 : 'grey', # white
269 7 : 'grey', # white
270 8 : 'grey', # black (bright)
270 8 : 'grey', # black (bright)
271 9 : 'red', # red (bright)
271 9 : 'red', # red (bright)
272 10 : 'lime', # green (bright)
272 10 : 'lime', # green (bright)
273 11 : 'yellow', # yellow (bright)
273 11 : 'yellow', # yellow (bright)
274 12 : 'deepskyblue', # blue (bright)
274 12 : 'deepskyblue', # blue (bright)
275 13 : 'magenta', # magenta (bright)
275 13 : 'magenta', # magenta (bright)
276 14 : 'cyan', # cyan (bright)
276 14 : 'cyan', # cyan (bright)
277 15 : 'white' } # white (bright)
277 15 : 'white' } # white (bright)
278
278
279 # Set the default color map for super class.
279 # Set the default color map for super class.
280 default_color_map = darkbg_color_map.copy()
280 default_color_map = darkbg_color_map.copy()
281
281
282 def get_color(self, color, intensity=0):
282 def get_color(self, color, intensity=0):
283 """ Returns a QColor for a given color code, or None if one cannot be
283 """ Returns a QColor for a given color code, or None if one cannot be
284 constructed.
284 constructed.
285 """
285 """
286 if color is None:
286 if color is None:
287 return None
287 return None
288
288
289 # Adjust for intensity, if possible.
289 # Adjust for intensity, if possible.
290 if color < 8 and intensity > 0:
290 if color < 8 and intensity > 0:
291 color += 8
291 color += 8
292
292
293 constructor = self.color_map.get(color, None)
293 constructor = self.color_map.get(color, None)
294 if isinstance(constructor, basestring):
294 if isinstance(constructor, basestring):
295 # If this is an X11 color name, we just hope there is a close SVG
295 # If this is an X11 color name, we just hope there is a close SVG
296 # color name. We could use QColor's static method
296 # color name. We could use QColor's static method
297 # 'setAllowX11ColorNames()', but this is global and only available
297 # 'setAllowX11ColorNames()', but this is global and only available
298 # on X11. It seems cleaner to aim for uniformity of behavior.
298 # on X11. It seems cleaner to aim for uniformity of behavior.
299 return QtGui.QColor(constructor)
299 return QtGui.QColor(constructor)
300
300
301 elif isinstance(constructor, (tuple, list)):
301 elif isinstance(constructor, (tuple, list)):
302 return QtGui.QColor(*constructor)
302 return QtGui.QColor(*constructor)
303
303
304 return None
304 return None
305
305
306 def get_format(self):
306 def get_format(self):
307 """ Returns a QTextCharFormat that encodes the current style attributes.
307 """ Returns a QTextCharFormat that encodes the current style attributes.
308 """
308 """
309 format = QtGui.QTextCharFormat()
309 format = QtGui.QTextCharFormat()
310
310
311 # Set foreground color
311 # Set foreground color
312 qcolor = self.get_color(self.foreground_color, self.intensity)
312 qcolor = self.get_color(self.foreground_color, self.intensity)
313 if qcolor is not None:
313 if qcolor is not None:
314 format.setForeground(qcolor)
314 format.setForeground(qcolor)
315
315
316 # Set background color
316 # Set background color
317 qcolor = self.get_color(self.background_color, self.intensity)
317 qcolor = self.get_color(self.background_color, self.intensity)
318 if qcolor is not None:
318 if qcolor is not None:
319 format.setBackground(qcolor)
319 format.setBackground(qcolor)
320
320
321 # Set font weight/style options
321 # Set font weight/style options
322 if self.bold:
322 if self.bold:
323 format.setFontWeight(QtGui.QFont.Bold)
323 format.setFontWeight(QtGui.QFont.Bold)
324 else:
324 else:
325 format.setFontWeight(QtGui.QFont.Normal)
325 format.setFontWeight(QtGui.QFont.Normal)
326 format.setFontItalic(self.italic)
326 format.setFontItalic(self.italic)
327 format.setFontUnderline(self.underline)
327 format.setFontUnderline(self.underline)
328
328
329 return format
329 return format
330
330
331 def set_background_color(self, color):
331 def set_background_color(self, color):
332 """ Given a background color (a QColor), attempt to set a color map
332 """ Given a background color (a QColor), attempt to set a color map
333 that will be aesthetically pleasing.
333 that will be aesthetically pleasing.
334 """
334 """
335 # Set a new default color map.
335 # Set a new default color map.
336 self.default_color_map = self.darkbg_color_map.copy()
336 self.default_color_map = self.darkbg_color_map.copy()
337
337
338 if color.value() >= 127:
338 if color.value() >= 127:
339 # Colors appropriate for a terminal with a light background. For
339 # Colors appropriate for a terminal with a light background. For
340 # now, only use non-bright colors...
340 # now, only use non-bright colors...
341 for i in xrange(8):
341 for i in xrange(8):
342 self.default_color_map[i + 8] = self.default_color_map[i]
342 self.default_color_map[i + 8] = self.default_color_map[i]
343
343
344 # ...and replace white with black.
344 # ...and replace white with black.
345 self.default_color_map[7] = self.default_color_map[15] = 'black'
345 self.default_color_map[7] = self.default_color_map[15] = 'black'
346
346
347 # Update the current color map with the new defaults.
347 # Update the current color map with the new defaults.
348 self.color_map.update(self.default_color_map)
348 self.color_map.update(self.default_color_map)
@@ -1,125 +1,134 b''
1 # Standard library imports
1 # Standard library imports
2 import unittest
2 import unittest
3
3
4 # Local imports
4 # Local imports
5 from IPython.frontend.qt.console.ansi_code_processor import AnsiCodeProcessor
5 from IPython.frontend.qt.console.ansi_code_processor import AnsiCodeProcessor
6
6
7
7
8 class TestAnsiCodeProcessor(unittest.TestCase):
8 class TestAnsiCodeProcessor(unittest.TestCase):
9
9
10 def setUp(self):
10 def setUp(self):
11 self.processor = AnsiCodeProcessor()
11 self.processor = AnsiCodeProcessor()
12
12
13 def test_clear(self):
13 def test_clear(self):
14 """ Do control sequences for clearing the console work?
14 """ Do control sequences for clearing the console work?
15 """
15 """
16 string = '\x1b[2J\x1b[K'
16 string = '\x1b[2J\x1b[K'
17 i = -1
17 i = -1
18 for i, substring in enumerate(self.processor.split_string(string)):
18 for i, substring in enumerate(self.processor.split_string(string)):
19 if i == 0:
19 if i == 0:
20 self.assertEquals(len(self.processor.actions), 1)
20 self.assertEquals(len(self.processor.actions), 1)
21 action = self.processor.actions[0]
21 action = self.processor.actions[0]
22 self.assertEquals(action.action, 'erase')
22 self.assertEquals(action.action, 'erase')
23 self.assertEquals(action.area, 'screen')
23 self.assertEquals(action.area, 'screen')
24 self.assertEquals(action.erase_to, 'all')
24 self.assertEquals(action.erase_to, 'all')
25 elif i == 1:
25 elif i == 1:
26 self.assertEquals(len(self.processor.actions), 1)
26 self.assertEquals(len(self.processor.actions), 1)
27 action = self.processor.actions[0]
27 action = self.processor.actions[0]
28 self.assertEquals(action.action, 'erase')
28 self.assertEquals(action.action, 'erase')
29 self.assertEquals(action.area, 'line')
29 self.assertEquals(action.area, 'line')
30 self.assertEquals(action.erase_to, 'end')
30 self.assertEquals(action.erase_to, 'end')
31 else:
31 else:
32 self.fail('Too many substrings.')
32 self.fail('Too many substrings.')
33 self.assertEquals(i, 1, 'Too few substrings.')
33 self.assertEquals(i, 1, 'Too few substrings.')
34
34
35 def test_colors(self):
35 def test_colors(self):
36 """ Do basic controls sequences for colors work?
36 """ Do basic controls sequences for colors work?
37 """
37 """
38 string = 'first\x1b[34mblue\x1b[0mlast'
38 string = 'first\x1b[34mblue\x1b[0mlast'
39 i = -1
39 i = -1
40 for i, substring in enumerate(self.processor.split_string(string)):
40 for i, substring in enumerate(self.processor.split_string(string)):
41 if i == 0:
41 if i == 0:
42 self.assertEquals(substring, 'first')
42 self.assertEquals(substring, 'first')
43 self.assertEquals(self.processor.foreground_color, None)
43 self.assertEquals(self.processor.foreground_color, None)
44 elif i == 1:
44 elif i == 1:
45 self.assertEquals(substring, 'blue')
45 self.assertEquals(substring, 'blue')
46 self.assertEquals(self.processor.foreground_color, 4)
46 self.assertEquals(self.processor.foreground_color, 4)
47 elif i == 2:
47 elif i == 2:
48 self.assertEquals(substring, 'last')
48 self.assertEquals(substring, 'last')
49 self.assertEquals(self.processor.foreground_color, None)
49 self.assertEquals(self.processor.foreground_color, None)
50 else:
50 else:
51 self.fail('Too many substrings.')
51 self.fail('Too many substrings.')
52 self.assertEquals(i, 2, 'Too few substrings.')
52 self.assertEquals(i, 2, 'Too few substrings.')
53
53
54 def test_colors_xterm(self):
54 def test_colors_xterm(self):
55 """ Do xterm-specific control sequences for colors work?
55 """ Do xterm-specific control sequences for colors work?
56 """
56 """
57 string = '\x1b]4;20;rgb:ff/ff/ff\x1b' \
57 string = '\x1b]4;20;rgb:ff/ff/ff\x1b' \
58 '\x1b]4;25;rgbi:1.0/1.0/1.0\x1b'
58 '\x1b]4;25;rgbi:1.0/1.0/1.0\x1b'
59 substrings = list(self.processor.split_string(string))
59 substrings = list(self.processor.split_string(string))
60 desired = { 20 : (255, 255, 255),
60 desired = { 20 : (255, 255, 255),
61 25 : (255, 255, 255) }
61 25 : (255, 255, 255) }
62 self.assertEquals(self.processor.color_map, desired)
62 self.assertEquals(self.processor.color_map, desired)
63
63
64 string = '\x1b[38;5;20m\x1b[48;5;25m'
64 string = '\x1b[38;5;20m\x1b[48;5;25m'
65 substrings = list(self.processor.split_string(string))
65 substrings = list(self.processor.split_string(string))
66 self.assertEquals(self.processor.foreground_color, 20)
66 self.assertEquals(self.processor.foreground_color, 20)
67 self.assertEquals(self.processor.background_color, 25)
67 self.assertEquals(self.processor.background_color, 25)
68
68
69 def test_scroll(self):
69 def test_scroll(self):
70 """ Do control sequences for scrolling the buffer work?
70 """ Do control sequences for scrolling the buffer work?
71 """
71 """
72 string = '\x1b[5S\x1b[T'
72 string = '\x1b[5S\x1b[T'
73 i = -1
73 i = -1
74 for i, substring in enumerate(self.processor.split_string(string)):
74 for i, substring in enumerate(self.processor.split_string(string)):
75 if i == 0:
75 if i == 0:
76 self.assertEquals(len(self.processor.actions), 1)
76 self.assertEquals(len(self.processor.actions), 1)
77 action = self.processor.actions[0]
77 action = self.processor.actions[0]
78 self.assertEquals(action.action, 'scroll')
78 self.assertEquals(action.action, 'scroll')
79 self.assertEquals(action.dir, 'up')
79 self.assertEquals(action.dir, 'up')
80 self.assertEquals(action.unit, 'line')
80 self.assertEquals(action.unit, 'line')
81 self.assertEquals(action.count, 5)
81 self.assertEquals(action.count, 5)
82 elif i == 1:
82 elif i == 1:
83 self.assertEquals(len(self.processor.actions), 1)
83 self.assertEquals(len(self.processor.actions), 1)
84 action = self.processor.actions[0]
84 action = self.processor.actions[0]
85 self.assertEquals(action.action, 'scroll')
85 self.assertEquals(action.action, 'scroll')
86 self.assertEquals(action.dir, 'down')
86 self.assertEquals(action.dir, 'down')
87 self.assertEquals(action.unit, 'line')
87 self.assertEquals(action.unit, 'line')
88 self.assertEquals(action.count, 1)
88 self.assertEquals(action.count, 1)
89 else:
89 else:
90 self.fail('Too many substrings.')
90 self.fail('Too many substrings.')
91 self.assertEquals(i, 1, 'Too few substrings.')
91 self.assertEquals(i, 1, 'Too few substrings.')
92
92
93 def test_formfeed(self):
93 def test_formfeed(self):
94 """ Are formfeed characters processed correctly?
94 """ Are formfeed characters processed correctly?
95 """
95 """
96 string = '\f' # form feed
96 string = '\f' # form feed
97 self.assertEquals(list(self.processor.split_string(string)), [''])
97 self.assertEquals(list(self.processor.split_string(string)), [''])
98 self.assertEquals(len(self.processor.actions), 1)
98 self.assertEquals(len(self.processor.actions), 1)
99 action = self.processor.actions[0]
99 action = self.processor.actions[0]
100 self.assertEquals(action.action, 'scroll')
100 self.assertEquals(action.action, 'scroll')
101 self.assertEquals(action.dir, 'down')
101 self.assertEquals(action.dir, 'down')
102 self.assertEquals(action.unit, 'page')
102 self.assertEquals(action.unit, 'page')
103 self.assertEquals(action.count, 1)
103 self.assertEquals(action.count, 1)
104
104
105 def test_carriage_return(self):
105 def test_carriage_return(self):
106 """ Are carriage return characters processed correctly?
106 """ Are carriage return characters processed correctly?
107 """
107 """
108 string = 'foo\rbar' # form feed
108 string = 'foo\rbar' # carriage return
109 self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar'])
109 self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar'])
110 self.assertEquals(len(self.processor.actions), 1)
110 self.assertEquals(len(self.processor.actions), 1)
111 action = self.processor.actions[0]
111 action = self.processor.actions[0]
112 self.assertEquals(action.action, 'carriage-return')
112 self.assertEquals(action.action, 'carriage-return')
113
113
114 def test_carriage_return_newline(self):
115 """transform CRLF to LF"""
116 string = 'foo\rbar\r\ncat\r\n' # carriage return and newline
117 # only one CR action should occur, and '\r\n' should transform to '\n'
118 self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar\r\ncat\r\n'])
119 self.assertEquals(len(self.processor.actions), 1)
120 action = self.processor.actions[0]
121 self.assertEquals(action.action, 'carriage-return')
122
114 def test_beep(self):
123 def test_beep(self):
115 """ Are beep characters processed correctly?
124 """ Are beep characters processed correctly?
116 """
125 """
117 string = 'foo\bbar' # form feed
126 string = 'foo\bbar' # form feed
118 self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar'])
127 self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar'])
119 self.assertEquals(len(self.processor.actions), 1)
128 self.assertEquals(len(self.processor.actions), 1)
120 action = self.processor.actions[0]
129 action = self.processor.actions[0]
121 self.assertEquals(action.action, 'beep')
130 self.assertEquals(action.action, 'beep')
122
131
123
132
124 if __name__ == '__main__':
133 if __name__ == '__main__':
125 unittest.main()
134 unittest.main()
General Comments 0
You need to be logged in to leave comments. Login now