##// END OF EJS Templates
Added support for scroll sequences and form feeds to AnsiCodeProcessor....
epatters -
Show More
@@ -1,34 +1,38 b''
1 """ Utilities for processing ANSI escape codes and special ASCII characters.
2 """
3 #-----------------------------------------------------------------------------
4 # Imports
5 #-----------------------------------------------------------------------------
6
1 # Standard library imports
7 # Standard library imports
8 from collections import namedtuple
2 import re
9 import re
3
10
4 # System library imports
11 # System library imports
5 from PyQt4 import QtCore, QtGui
12 from PyQt4 import QtCore, QtGui
6
13
14 #-----------------------------------------------------------------------------
15 # Constants and datatypes
16 #-----------------------------------------------------------------------------
7
17
8 class AnsiAction(object):
18 # An action for erase requests (ED and EL commands).
9 """ Represents an action requested by an ANSI escape sequence.
19 EraseAction = namedtuple('EraseAction', ['action', 'area', 'erase_to'])
10 """
11 def __init__(self, kind):
12 self.kind = kind
13
20
14 class MoveAction(AnsiAction):
21 # An action for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL, CHA, CUP,
15 """ An AnsiAction for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL,
22 # and HVP commands).
16 CHA, and CUP commands).
23 # FIXME: Not implemented in AnsiCodeProcessor.
17 """
24 MoveAction = namedtuple('MoveAction', ['action', 'dir', 'unit', 'count'])
18 def __init__(self):
19 raise NotImplementedError
20
25
21 class EraseAction(AnsiAction):
26 # An action for scroll requests (SU and ST) and form feeds.
22 """ An AnsiAction for erase requests (ED and EL commands).
27 ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count'])
23 """
24 def __init__(self, area, erase_to):
25 super(EraseAction, self).__init__('erase')
26 self.area = area
27 self.erase_to = erase_to
28
28
29 #-----------------------------------------------------------------------------
30 # Classes
31 #-----------------------------------------------------------------------------
29
32
30 class AnsiCodeProcessor(object):
33 class AnsiCodeProcessor(object):
31 """ Translates ANSI escape codes into readable attributes.
34 """ Translates special ASCII characters and ANSI escape codes into readable
35 attributes.
32 """
36 """
33
37
34 # Whether to increase intensity or set boldness for SGR code 1.
38 # Whether to increase intensity or set boldness for SGR code 1.
@@ -38,6 +42,11 b' class AnsiCodeProcessor(object):'
38 # Protected class variables.
42 # Protected class variables.
39 _ansi_commands = 'ABCDEFGHJKSTfmnsu'
43 _ansi_commands = 'ABCDEFGHJKSTfmnsu'
40 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands)
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 def __init__(self):
51 def __init__(self):
43 self.actions = []
52 self.actions = []
@@ -60,7 +69,8 b' class AnsiCodeProcessor(object):'
60 start = 0
69 start = 0
61
70
62 for match in self._ansi_pattern.finditer(string):
71 for match in self._ansi_pattern.finditer(string):
63 substring = string[start:match.start()]
72 raw = string[start:match.start()]
73 substring = self._special_pattern.sub(self._replace_special, raw)
64 if substring or self.actions:
74 if substring or self.actions:
65 yield substring
75 yield substring
66 start = match.end()
76 start = match.end()
@@ -77,7 +87,8 b' class AnsiCodeProcessor(object):'
77 else:
87 else:
78 self.set_csi_code(match.group(2), params)
88 self.set_csi_code(match.group(2), params)
79
89
80 substring = string[start:]
90 raw = string[start:]
91 substring = self._special_pattern.sub(self._replace_special, raw)
81 if substring or self.actions:
92 if substring or self.actions:
82 yield substring
93 yield substring
83
94
@@ -107,7 +118,13 b' class AnsiCodeProcessor(object):'
107 erase_to = 'start'
118 erase_to = 'start'
108 elif code == 2:
119 elif code == 2:
109 erase_to = 'all'
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 def set_sgr_code(self, code):
129 def set_sgr_code(self, code):
113 """ Set attributes based on SGR (Select Graphic Rendition) code.
130 """ Set attributes based on SGR (Select Graphic Rendition) code.
@@ -140,6 +157,16 b' class AnsiCodeProcessor(object):'
140 self.background_color = code - 40
157 self.background_color = code - 40
141 elif code == 49:
158 elif code == 49:
142 self.background_color = None
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 class QtAnsiCodeProcessor(AnsiCodeProcessor):
172 class QtAnsiCodeProcessor(AnsiCodeProcessor):
@@ -1257,8 +1257,9 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
1257 cursor.beginEditBlock()
1257 cursor.beginEditBlock()
1258 if self.ansi_codes:
1258 if self.ansi_codes:
1259 for substring in self._ansi_processor.split_string(text):
1259 for substring in self._ansi_processor.split_string(text):
1260 for action in self._ansi_processor.actions:
1260 for act in self._ansi_processor.actions:
1261 if action.kind == 'erase' and action.area == 'screen':
1261 if ((act.action == 'erase' and act.area == 'screen') or
1262 (act.action == 'scroll' and act.unit == 'page')):
1262 cursor.select(QtGui.QTextCursor.Document)
1263 cursor.select(QtGui.QTextCursor.Document)
1263 cursor.removeSelectedText()
1264 cursor.removeSelectedText()
1264 format = self._ansi_processor.get_format()
1265 format = self._ansi_processor.get_format()
@@ -17,13 +17,13 b' class TestAnsiCodeProcessor(unittest.TestCase):'
17 if i == 0:
17 if i == 0:
18 self.assertEquals(len(self.processor.actions), 1)
18 self.assertEquals(len(self.processor.actions), 1)
19 action = self.processor.actions[0]
19 action = self.processor.actions[0]
20 self.assertEquals(action.kind, 'erase')
20 self.assertEquals(action.action, 'erase')
21 self.assertEquals(action.area, 'screen')
21 self.assertEquals(action.area, 'screen')
22 self.assertEquals(action.erase_to, 'all')
22 self.assertEquals(action.erase_to, 'all')
23 elif i == 1:
23 elif i == 1:
24 self.assertEquals(len(self.processor.actions), 1)
24 self.assertEquals(len(self.processor.actions), 1)
25 action = self.processor.actions[0]
25 action = self.processor.actions[0]
26 self.assertEquals(action.kind, 'erase')
26 self.assertEquals(action.action, 'erase')
27 self.assertEquals(action.area, 'line')
27 self.assertEquals(action.area, 'line')
28 self.assertEquals(action.erase_to, 'end')
28 self.assertEquals(action.erase_to, 'end')
29 else:
29 else:
@@ -47,6 +47,38 b' class TestAnsiCodeProcessor(unittest.TestCase):'
47 self.fail('Too many substrings.')
47 self.fail('Too many substrings.')
48 self.assertEquals(i, 2, 'Too few substrings.')
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 if __name__ == '__main__':
83 if __name__ == '__main__':
52 unittest.main()
84 unittest.main()
General Comments 0
You need to be logged in to leave comments. Login now