##// END OF EJS Templates
Added support for ANSI erase codes. Clearing the console via ANSI escape sequences is now supported.
epatters -
Show More
@@ -5,6 +5,28 b' import re'
5 5 from PyQt4 import QtCore, QtGui
6 6
7 7
8 class AnsiAction(object):
9 """ Represents an action requested by an ANSI escape sequence.
10 """
11 def __init__(self, kind):
12 self.kind = kind
13
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
20
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
28
29
8 30 class AnsiCodeProcessor(object):
9 31 """ Translates ANSI escape codes into readable attributes.
10 32 """
@@ -14,10 +36,11 b' class AnsiCodeProcessor(object):'
14 36 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands)
15 37
16 38 def __init__(self):
17 self.reset()
39 self.actions = []
40 self.reset_sgr()
18 41
19 def reset(self):
20 """ Reset attributs to their default values.
42 def reset_sgr(self):
43 """ Reset graphics attributs to their default values.
21 44 """
22 45 self.intensity = 0
23 46 self.italic = False
@@ -29,19 +52,29 b' class AnsiCodeProcessor(object):'
29 52 def split_string(self, string):
30 53 """ Yields substrings for which the same escape code applies.
31 54 """
55 self.actions = []
32 56 start = 0
33 57
34 58 for match in self._ansi_pattern.finditer(string):
35 59 substring = string[start:match.start()]
36 if substring:
60 if substring or self.actions:
37 61 yield substring
38 62 start = match.end()
39 63
40 params = map(int, match.group(1).split(';'))
64 self.actions = []
65 try:
66 params = []
67 for param in match.group(1).split(';'):
68 if param:
69 params.append(int(param))
70 except ValueError:
71 # Silently discard badly formed escape codes.
72 pass
73 else:
41 74 self.set_csi_code(match.group(2), params)
42 75
43 76 substring = string[start:]
44 if substring:
77 if substring or self.actions:
45 78 yield substring
46 79
47 80 def set_csi_code(self, command, params=[]):
@@ -59,11 +92,24 b' class AnsiCodeProcessor(object):'
59 92 for code in params:
60 93 self.set_sgr_code(code)
61 94
95 elif (command == 'J' or # ED - Erase Data
96 command == 'K'): # EL - Erase in Line
97 code = params[0] if params else 0
98 if 0 <= code <= 2:
99 area = 'screen' if command == 'J' else 'line'
100 if code == 0:
101 erase_to = 'end'
102 elif code == 1:
103 erase_to = 'start'
104 elif code == 2:
105 erase_to = 'all'
106 self.actions.append(EraseAction(area, erase_to))
107
62 108 def set_sgr_code(self, code):
63 109 """ Set attributes based on SGR (Select Graphic Rendition) code.
64 110 """
65 111 if code == 0:
66 self.reset()
112 self.reset_sgr()
67 113 elif code == 1:
68 114 self.intensity = 1
69 115 self.bold = True
@@ -895,6 +895,10 b' class ConsoleWidget(QtGui.QWidget):'
895 895 cursor.beginEditBlock()
896 896 if self.ansi_codes:
897 897 for substring in self._ansi_processor.split_string(text):
898 for action in self._ansi_processor.actions:
899 if action.kind == 'erase' and action.area == 'screen':
900 cursor.select(QtGui.QTextCursor.Document)
901 cursor.removeSelectedText()
898 902 format = self._ansi_processor.get_format()
899 903 cursor.insertText(substring, format)
900 904 else:
@@ -54,7 +54,7 b' def main():'
54 54 from ipython_widget import IPythonWidget
55 55 widget = IPythonWidget()
56 56 widget.kernel_manager = kernel_manager
57 widget.setWindowTitle('Python')
57 widget.setWindowTitle('Python' if namespace.pure else 'IPython')
58 58 widget.show()
59 59 app.exec_()
60 60
@@ -10,6 +10,26 b' class TestAnsiCodeProcessor(unittest.TestCase):'
10 10 def setUp(self):
11 11 self.processor = AnsiCodeProcessor()
12 12
13 def testClear(self):
14 string = '\x1b[2J\x1b[K'
15 i = -1
16 for i, substring in enumerate(self.processor.split_string(string)):
17 if i == 0:
18 self.assertEquals(len(self.processor.actions), 1)
19 action = self.processor.actions[0]
20 self.assertEquals(action.kind, 'erase')
21 self.assertEquals(action.area, 'screen')
22 self.assertEquals(action.erase_to, 'all')
23 elif i == 1:
24 self.assertEquals(len(self.processor.actions), 1)
25 action = self.processor.actions[0]
26 self.assertEquals(action.kind, 'erase')
27 self.assertEquals(action.area, 'line')
28 self.assertEquals(action.erase_to, 'end')
29 else:
30 self.fail('Too many substrings.')
31 self.assertEquals(i, 1, 'Too few substrings.')
32
13 33 def testColors(self):
14 34 string = "first\x1b[34mblue\x1b[0mlast"
15 35 i = -1
@@ -24,8 +44,8 b' class TestAnsiCodeProcessor(unittest.TestCase):'
24 44 self.assertEquals(substring, 'last')
25 45 self.assertEquals(self.processor.foreground_color, None)
26 46 else:
27 self.fail("Too many substrings.")
28 self.assertEquals(i, 2, "Too few substrings.")
47 self.fail('Too many substrings.')
48 self.assertEquals(i, 2, 'Too few substrings.')
29 49
30 50
31 51 if __name__ == '__main__':
General Comments 0
You need to be logged in to leave comments. Login now