Show More
@@ -1,34 +1,38 | |||
|
1 | """ Utilities for processing ANSI escape codes and special ASCII characters. | |
|
2 | """ | |
|
3 | #----------------------------------------------------------------------------- | |
|
4 | # Imports | |
|
5 | #----------------------------------------------------------------------------- | |
|
6 | ||
|
1 | 7 | # Standard library imports |
|
8 | from collections import namedtuple | |
|
2 | 9 | import re |
|
3 | 10 | |
|
4 | 11 | # System library imports |
|
5 | 12 | from PyQt4 import QtCore, QtGui |
|
6 | 13 | |
|
14 | #----------------------------------------------------------------------------- | |
|
15 | # Constants and datatypes | |
|
16 | #----------------------------------------------------------------------------- | |
|
7 | 17 | |
|
8 | class AnsiAction(object): | |
|
9 | """ Represents an action requested by an ANSI escape sequence. | |
|
10 | """ | |
|
11 | def __init__(self, kind): | |
|
12 | self.kind = kind | |
|
18 | # An action for erase requests (ED and EL commands). | |
|
19 | EraseAction = namedtuple('EraseAction', ['action', 'area', 'erase_to']) | |
|
13 | 20 | |
|
14 | class MoveAction(AnsiAction): | |
|
15 | """ An AnsiAction for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL, | |
|
16 | CHA, and CUP commands). | |
|
17 | """ | |
|
18 | def __init__(self): | |
|
19 | raise NotImplementedError | |
|
21 | # An action for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL, CHA, CUP, | |
|
22 | # and HVP commands). | |
|
23 | # FIXME: Not implemented in AnsiCodeProcessor. | |
|
24 | MoveAction = namedtuple('MoveAction', ['action', 'dir', 'unit', 'count']) | |
|
20 | 25 | |
|
21 | class EraseAction(AnsiAction): | |
|
22 | """ An AnsiAction for erase requests (ED and EL commands). | |
|
23 | """ | |
|
24 | def __init__(self, area, erase_to): | |
|
25 | super(EraseAction, self).__init__('erase') | |
|
26 | self.area = area | |
|
27 | self.erase_to = erase_to | |
|
26 | # An action for scroll requests (SU and ST) and form feeds. | |
|
27 | ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count']) | |
|
28 | 28 | |
|
29 | #----------------------------------------------------------------------------- | |
|
30 | # Classes | |
|
31 | #----------------------------------------------------------------------------- | |
|
29 | 32 | |
|
30 | 33 | class AnsiCodeProcessor(object): |
|
31 |
""" Translates ANSI escape codes into readable |
|
|
34 | """ Translates special ASCII characters and ANSI escape codes into readable | |
|
35 | attributes. | |
|
32 | 36 | """ |
|
33 | 37 | |
|
34 | 38 | # Whether to increase intensity or set boldness for SGR code 1. |
@@ -38,6 +42,11 class AnsiCodeProcessor(object): | |||
|
38 | 42 | # Protected class variables. |
|
39 | 43 | _ansi_commands = 'ABCDEFGHJKSTfmnsu' |
|
40 | 44 | _ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands) |
|
45 | _special_pattern = re.compile('([\f])') | |
|
46 | ||
|
47 | #--------------------------------------------------------------------------- | |
|
48 | # AnsiCodeProcessor interface | |
|
49 | #--------------------------------------------------------------------------- | |
|
41 | 50 | |
|
42 | 51 | def __init__(self): |
|
43 | 52 | self.actions = [] |
@@ -60,7 +69,8 class AnsiCodeProcessor(object): | |||
|
60 | 69 | start = 0 |
|
61 | 70 | |
|
62 | 71 | for match in self._ansi_pattern.finditer(string): |
|
63 |
|
|
|
72 | raw = string[start:match.start()] | |
|
73 | substring = self._special_pattern.sub(self._replace_special, raw) | |
|
64 | 74 | if substring or self.actions: |
|
65 | 75 | yield substring |
|
66 | 76 | start = match.end() |
@@ -77,7 +87,8 class AnsiCodeProcessor(object): | |||
|
77 | 87 | else: |
|
78 | 88 | self.set_csi_code(match.group(2), params) |
|
79 | 89 | |
|
80 |
|
|
|
90 | raw = string[start:] | |
|
91 | substring = self._special_pattern.sub(self._replace_special, raw) | |
|
81 | 92 | if substring or self.actions: |
|
82 | 93 | yield substring |
|
83 | 94 | |
@@ -107,7 +118,13 class AnsiCodeProcessor(object): | |||
|
107 | 118 | erase_to = 'start' |
|
108 | 119 | elif code == 2: |
|
109 | 120 | erase_to = 'all' |
|
110 | self.actions.append(EraseAction(area, erase_to)) | |
|
121 | self.actions.append(EraseAction('erase', area, erase_to)) | |
|
122 | ||
|
123 | elif (command == 'S' or # SU - Scroll Up | |
|
124 | command == 'T'): # SD - Scroll Down | |
|
125 | dir = 'up' if command == 'S' else 'down' | |
|
126 | count = params[0] if params else 1 | |
|
127 | self.actions.append(ScrollAction('scroll', dir, 'line', count)) | |
|
111 | 128 | |
|
112 | 129 | def set_sgr_code(self, code): |
|
113 | 130 | """ Set attributes based on SGR (Select Graphic Rendition) code. |
@@ -140,6 +157,16 class AnsiCodeProcessor(object): | |||
|
140 | 157 | self.background_color = code - 40 |
|
141 | 158 | elif code == 49: |
|
142 | 159 | self.background_color = None |
|
160 | ||
|
161 | #--------------------------------------------------------------------------- | |
|
162 | # Protected interface | |
|
163 | #--------------------------------------------------------------------------- | |
|
164 | ||
|
165 | def _replace_special(self, match): | |
|
166 | special = match.group(1) | |
|
167 | if special == '\f': | |
|
168 | self.actions.append(ScrollAction('scroll', 'down', 'page', 1)) | |
|
169 | return '' | |
|
143 | 170 | |
|
144 | 171 | |
|
145 | 172 | class QtAnsiCodeProcessor(AnsiCodeProcessor): |
@@ -1257,8 +1257,9 class ConsoleWidget(Configurable, QtGui.QWidget): | |||
|
1257 | 1257 | cursor.beginEditBlock() |
|
1258 | 1258 | if self.ansi_codes: |
|
1259 | 1259 | for substring in self._ansi_processor.split_string(text): |
|
1260 |
for act |
|
|
1261 |
if action |
|
|
1260 | for act in self._ansi_processor.actions: | |
|
1261 | if ((act.action == 'erase' and act.area == 'screen') or | |
|
1262 | (act.action == 'scroll' and act.unit == 'page')): | |
|
1262 | 1263 | cursor.select(QtGui.QTextCursor.Document) |
|
1263 | 1264 | cursor.removeSelectedText() |
|
1264 | 1265 | format = self._ansi_processor.get_format() |
@@ -17,13 +17,13 class TestAnsiCodeProcessor(unittest.TestCase): | |||
|
17 | 17 | if i == 0: |
|
18 | 18 | self.assertEquals(len(self.processor.actions), 1) |
|
19 | 19 | action = self.processor.actions[0] |
|
20 |
self.assertEquals(action. |
|
|
20 | self.assertEquals(action.action, 'erase') | |
|
21 | 21 | self.assertEquals(action.area, 'screen') |
|
22 | 22 | self.assertEquals(action.erase_to, 'all') |
|
23 | 23 | elif i == 1: |
|
24 | 24 | self.assertEquals(len(self.processor.actions), 1) |
|
25 | 25 | action = self.processor.actions[0] |
|
26 |
self.assertEquals(action. |
|
|
26 | self.assertEquals(action.action, 'erase') | |
|
27 | 27 | self.assertEquals(action.area, 'line') |
|
28 | 28 | self.assertEquals(action.erase_to, 'end') |
|
29 | 29 | else: |
@@ -47,6 +47,38 class TestAnsiCodeProcessor(unittest.TestCase): | |||
|
47 | 47 | self.fail('Too many substrings.') |
|
48 | 48 | self.assertEquals(i, 2, 'Too few substrings.') |
|
49 | 49 | |
|
50 | def testScroll(self): | |
|
51 | string = '\x1b[5S\x1b[T' | |
|
52 | i = -1 | |
|
53 | for i, substring in enumerate(self.processor.split_string(string)): | |
|
54 | if i == 0: | |
|
55 | self.assertEquals(len(self.processor.actions), 1) | |
|
56 | action = self.processor.actions[0] | |
|
57 | self.assertEquals(action.action, 'scroll') | |
|
58 | self.assertEquals(action.dir, 'up') | |
|
59 | self.assertEquals(action.unit, 'line') | |
|
60 | self.assertEquals(action.count, 5) | |
|
61 | elif i == 1: | |
|
62 | self.assertEquals(len(self.processor.actions), 1) | |
|
63 | action = self.processor.actions[0] | |
|
64 | self.assertEquals(action.action, 'scroll') | |
|
65 | self.assertEquals(action.dir, 'down') | |
|
66 | self.assertEquals(action.unit, 'line') | |
|
67 | self.assertEquals(action.count, 1) | |
|
68 | else: | |
|
69 | self.fail('Too many substrings.') | |
|
70 | self.assertEquals(i, 1, 'Too few substrings.') | |
|
71 | ||
|
72 | def testSpecials(self): | |
|
73 | string = '\f' # form feed | |
|
74 | self.assertEquals(list(self.processor.split_string(string)), ['']) | |
|
75 | self.assertEquals(len(self.processor.actions), 1) | |
|
76 | action = self.processor.actions[0] | |
|
77 | self.assertEquals(action.action, 'scroll') | |
|
78 | self.assertEquals(action.dir, 'down') | |
|
79 | self.assertEquals(action.unit, 'page') | |
|
80 | self.assertEquals(action.count, 1) | |
|
81 | ||
|
50 | 82 | |
|
51 | 83 | if __name__ == '__main__': |
|
52 | 84 | unittest.main() |
General Comments 0
You need to be logged in to leave comments.
Login now