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. |
|
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 = [] | |
|
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 | self.set_csi_code(match.group(2), params) |
|
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=[]): | |
@@ -59,11 +92,24 b' class AnsiCodeProcessor(object):' | |||||
59 | for code in params: |
|
92 | for code in params: | |
60 | self.set_sgr_code(code) |
|
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 | 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( |
|
47 | self.fail('Too many substrings.') | |
28 |
self.assertEquals(i, 2, |
|
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