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