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. |
|
|
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( |
|
|
28 |
self.assertEquals(i, 2, |
|
|
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