##// 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 from PyQt4 import QtCore, QtGui
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 class AnsiCodeProcessor(object):
30 class AnsiCodeProcessor(object):
9 """ Translates ANSI escape codes into readable attributes.
31 """ Translates ANSI escape codes into readable attributes.
10 """
32 """
@@ -14,10 +36,11 b' class AnsiCodeProcessor(object):'
14 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands)
36 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands)
15
37
16 def __init__(self):
38 def __init__(self):
17 self.reset()
39 self.actions = []
40 self.reset_sgr()
18
41
19 def reset(self):
42 def reset_sgr(self):
20 """ Reset attributs to their default values.
43 """ Reset graphics attributs to their default values.
21 """
44 """
22 self.intensity = 0
45 self.intensity = 0
23 self.italic = False
46 self.italic = False
@@ -29,19 +52,29 b' class AnsiCodeProcessor(object):'
29 def split_string(self, string):
52 def split_string(self, string):
30 """ Yields substrings for which the same escape code applies.
53 """ Yields substrings for which the same escape code applies.
31 """
54 """
55 self.actions = []
32 start = 0
56 start = 0
33
57
34 for match in self._ansi_pattern.finditer(string):
58 for match in self._ansi_pattern.finditer(string):
35 substring = string[start:match.start()]
59 substring = string[start:match.start()]
36 if substring:
60 if substring or self.actions:
37 yield substring
61 yield substring
38 start = match.end()
62 start = match.end()
39
63
40 params = map(int, match.group(1).split(';'))
64 self.actions = []
41 self.set_csi_code(match.group(2), params)
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:
74 self.set_csi_code(match.group(2), params)
42
75
43 substring = string[start:]
76 substring = string[start:]
44 if substring:
77 if substring or self.actions:
45 yield substring
78 yield substring
46
79
47 def set_csi_code(self, command, params=[]):
80 def set_csi_code(self, command, params=[]):
@@ -55,15 +88,28 b' class AnsiCodeProcessor(object):'
55 params : sequence of integers, optional
88 params : sequence of integers, optional
56 The parameter codes for the command.
89 The parameter codes for the command.
57 """
90 """
58 if command == 'm': # SGR - Select Graphic Rendition
91 if command == 'm': # SGR - Select Graphic Rendition
59 for code in params:
92 for code in params:
60 self.set_sgr_code(code)
93 self.set_sgr_code(code)
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))
61
107
62 def set_sgr_code(self, code):
108 def set_sgr_code(self, code):
63 """ Set attributes based on SGR (Select Graphic Rendition) code.
109 """ Set attributes based on SGR (Select Graphic Rendition) code.
64 """
110 """
65 if code == 0:
111 if code == 0:
66 self.reset()
112 self.reset_sgr()
67 elif code == 1:
113 elif code == 1:
68 self.intensity = 1
114 self.intensity = 1
69 self.bold = True
115 self.bold = True
@@ -895,6 +895,10 b' class ConsoleWidget(QtGui.QWidget):'
895 cursor.beginEditBlock()
895 cursor.beginEditBlock()
896 if self.ansi_codes:
896 if self.ansi_codes:
897 for substring in self._ansi_processor.split_string(text):
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 format = self._ansi_processor.get_format()
902 format = self._ansi_processor.get_format()
899 cursor.insertText(substring, format)
903 cursor.insertText(substring, format)
900 else:
904 else:
@@ -54,7 +54,7 b' def main():'
54 from ipython_widget import IPythonWidget
54 from ipython_widget import IPythonWidget
55 widget = IPythonWidget()
55 widget = IPythonWidget()
56 widget.kernel_manager = kernel_manager
56 widget.kernel_manager = kernel_manager
57 widget.setWindowTitle('Python')
57 widget.setWindowTitle('Python' if namespace.pure else 'IPython')
58 widget.show()
58 widget.show()
59 app.exec_()
59 app.exec_()
60
60
@@ -10,6 +10,26 b' class TestAnsiCodeProcessor(unittest.TestCase):'
10 def setUp(self):
10 def setUp(self):
11 self.processor = AnsiCodeProcessor()
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 def testColors(self):
33 def testColors(self):
14 string = "first\x1b[34mblue\x1b[0mlast"
34 string = "first\x1b[34mblue\x1b[0mlast"
15 i = -1
35 i = -1
@@ -24,8 +44,8 b' class TestAnsiCodeProcessor(unittest.TestCase):'
24 self.assertEquals(substring, 'last')
44 self.assertEquals(substring, 'last')
25 self.assertEquals(self.processor.foreground_color, None)
45 self.assertEquals(self.processor.foreground_color, None)
26 else:
46 else:
27 self.fail("Too many substrings.")
47 self.fail('Too many substrings.')
28 self.assertEquals(i, 2, "Too few substrings.")
48 self.assertEquals(i, 2, 'Too few substrings.')
29
49
30
50
31 if __name__ == '__main__':
51 if __name__ == '__main__':
General Comments 0
You need to be logged in to leave comments. Login now