Show More
@@ -29,16 +29,22 b" ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count'])" | |||
|
29 | 29 | # An action for the carriage return character |
|
30 | 30 | CarriageReturnAction = namedtuple('CarriageReturnAction', ['action']) |
|
31 | 31 | |
|
32 | # An action for the \n character | |
|
33 | NewLineAction = namedtuple('NewLineAction', ['action']) | |
|
34 | ||
|
32 | 35 | # An action for the beep character |
|
33 | 36 | BeepAction = namedtuple('BeepAction', ['action']) |
|
34 | 37 | |
|
38 | # An action for backspace | |
|
39 | BackSpaceAction = namedtuple('BackSpaceAction', ['action']) | |
|
40 | ||
|
35 | 41 | # Regular expressions. |
|
36 | 42 | CSI_COMMANDS = 'ABCDEFGHJKSTfmnsu' |
|
37 | 43 | CSI_SUBPATTERN = '\[(.*?)([%s])' % CSI_COMMANDS |
|
38 | 44 | OSC_SUBPATTERN = '\](.*?)[\x07\x1b]' |
|
39 | 45 | ANSI_PATTERN = ('\x01?\x1b(%s|%s)\x02?' % \ |
|
40 | 46 | (CSI_SUBPATTERN, OSC_SUBPATTERN)) |
|
41 | ANSI_OR_SPECIAL_PATTERN = re.compile('(\b|\r(?!\n))|(?:%s)' % ANSI_PATTERN) | |
|
47 | ANSI_OR_SPECIAL_PATTERN = re.compile('(\a|\b|\r(?!\n)|\r?\n)|(?:%s)' % ANSI_PATTERN) | |
|
42 | 48 | SPECIAL_PATTERN = re.compile('([\f])') |
|
43 | 49 | |
|
44 | 50 | #----------------------------------------------------------------------------- |
@@ -83,24 +89,41 b' class AnsiCodeProcessor(object):' | |||
|
83 | 89 | self.actions = [] |
|
84 | 90 | start = 0 |
|
85 | 91 | |
|
92 | # strings ending with \r are assumed to be ending in \r\n since | |
|
93 | # \n is appended to output strings automatically. Accounting | |
|
94 | # for that, here. | |
|
95 | last_char = '\n' if len(string) > 0 and string[-1] == '\n' else None | |
|
96 | string = string[:-1] if last_char is not None else string | |
|
97 | ||
|
86 | 98 | for match in ANSI_OR_SPECIAL_PATTERN.finditer(string): |
|
87 | 99 | raw = string[start:match.start()] |
|
88 | 100 | substring = SPECIAL_PATTERN.sub(self._replace_special, raw) |
|
89 | 101 | if substring or self.actions: |
|
90 | 102 | yield substring |
|
103 | self.actions = [] | |
|
91 | 104 | start = match.end() |
|
92 | 105 | |
|
93 | self.actions = [] | |
|
94 | 106 | groups = filter(lambda x: x is not None, match.groups()) |
|
95 |
|
|
|
96 | self.actions.append(CarriageReturnAction('carriage-return')) | |
|
97 | yield '' | |
|
98 | elif groups[0] == '\b': | |
|
107 | g0 = groups[0] | |
|
108 | if g0 == '\a': | |
|
99 | 109 | self.actions.append(BeepAction('beep')) |
|
100 |
yield |
|
|
110 | yield None | |
|
111 | self.actions = [] | |
|
112 | elif g0 == '\r': | |
|
113 | self.actions.append(CarriageReturnAction('carriage-return')) | |
|
114 | yield None | |
|
115 | self.actions = [] | |
|
116 | elif g0 == '\b': | |
|
117 | self.actions.append(BackSpaceAction('backspace')) | |
|
118 | yield None | |
|
119 | self.actions = [] | |
|
120 | elif g0 == '\n' or g0 == '\r\n': | |
|
121 | self.actions.append(NewLineAction('newline')) | |
|
122 | yield g0 | |
|
123 | self.actions = [] | |
|
101 | 124 | else: |
|
102 | 125 | params = [ param for param in groups[1].split(';') if param ] |
|
103 |
if g |
|
|
126 | if g0.startswith('['): | |
|
104 | 127 | # Case 1: CSI code. |
|
105 | 128 | try: |
|
106 | 129 | params = map(int, params) |
@@ -110,7 +133,7 b' class AnsiCodeProcessor(object):' | |||
|
110 | 133 | else: |
|
111 | 134 | self.set_csi_code(groups[2], params) |
|
112 | 135 | |
|
113 |
elif g |
|
|
136 | elif g0.startswith(']'): | |
|
114 | 137 | # Case 2: OSC code. |
|
115 | 138 | self.set_osc_code(params) |
|
116 | 139 | |
@@ -119,6 +142,10 b' class AnsiCodeProcessor(object):' | |||
|
119 | 142 | if substring or self.actions: |
|
120 | 143 | yield substring |
|
121 | 144 | |
|
145 | if last_char is not None: | |
|
146 | self.actions.append(NewLineAction('newline')) | |
|
147 | yield last_char | |
|
148 | ||
|
122 | 149 | def set_csi_code(self, command, params=[]): |
|
123 | 150 | """ Set attributes based on CSI (Control Sequence Introducer) code. |
|
124 | 151 |
@@ -1553,9 +1553,32 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):' | |||
|
1553 | 1553 | elif act.action == 'beep': |
|
1554 | 1554 | QtGui.qApp.beep() |
|
1555 | 1555 | |
|
1556 | elif act.action == 'backspace': | |
|
1557 | if not cursor.atBlockStart(): | |
|
1558 | cursor.movePosition( | |
|
1559 | cursor.PreviousCharacter, cursor.KeepAnchor) | |
|
1560 | ||
|
1561 | elif act.action == 'newline': | |
|
1562 | cursor.movePosition(cursor.EndOfLine) | |
|
1563 | ||
|
1556 | 1564 | format = self._ansi_processor.get_format() |
|
1565 | ||
|
1566 | selection = cursor.selectedText() | |
|
1567 | if len(selection) == 0: | |
|
1568 | cursor.insertText(substring, format) | |
|
1569 | elif substring is not None: | |
|
1570 | # BS and CR are treated as a change in print | |
|
1571 | # position, rather than a backwards character | |
|
1572 | # deletion for output equivalence with (I)Python | |
|
1573 | # terminal. | |
|
1574 | if len(substring) >= len(selection): | |
|
1557 | 1575 | cursor.insertText(substring, format) |
|
1558 | 1576 | else: |
|
1577 | old_text = selection[len(substring):] | |
|
1578 | cursor.insertText(substring + old_text, format) | |
|
1579 | cursor.movePosition(cursor.PreviousCharacter, | |
|
1580 | cursor.KeepAnchor, len(old_text)) | |
|
1581 | else: | |
|
1559 | 1582 | cursor.insertText(text) |
|
1560 | 1583 | cursor.endEditBlock() |
|
1561 | 1584 |
@@ -106,28 +106,65 b' class TestAnsiCodeProcessor(unittest.TestCase):' | |||
|
106 | 106 | """ Are carriage return characters processed correctly? |
|
107 | 107 | """ |
|
108 | 108 | string = 'foo\rbar' # carriage return |
|
109 | self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar']) | |
|
110 | self.assertEquals(len(self.processor.actions), 1) | |
|
111 |
|
|
|
112 | self.assertEquals(action.action, 'carriage-return') | |
|
109 | splits = [] | |
|
110 | actions = [] | |
|
111 | for split in self.processor.split_string(string): | |
|
112 | splits.append(split) | |
|
113 | actions.append([action.action for action in self.processor.actions]) | |
|
114 | self.assertEquals(splits, ['foo', None, 'bar']) | |
|
115 | self.assertEquals(actions, [[], ['carriage-return'], []]) | |
|
113 | 116 | |
|
114 | 117 | def test_carriage_return_newline(self): |
|
115 | 118 | """transform CRLF to LF""" |
|
116 | string = 'foo\rbar\r\ncat\r\n' # carriage return and newline | |
|
119 | string = 'foo\rbar\r\ncat\r\n\n' # carriage return and newline | |
|
117 | 120 | # only one CR action should occur, and '\r\n' should transform to '\n' |
|
118 | self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar\r\ncat\r\n']) | |
|
119 | self.assertEquals(len(self.processor.actions), 1) | |
|
120 |
|
|
|
121 | self.assertEquals(action.action, 'carriage-return') | |
|
121 | splits = [] | |
|
122 | actions = [] | |
|
123 | for split in self.processor.split_string(string): | |
|
124 | splits.append(split) | |
|
125 | actions.append([action.action for action in self.processor.actions]) | |
|
126 | self.assertEquals(splits, ['foo', None, 'bar', '\r\n', 'cat', '\r\n', '\n']) | |
|
127 | self.assertEquals(actions, [[], ['carriage-return'], [], ['newline'], [], ['newline'], ['newline']]) | |
|
122 | 128 | |
|
123 | 129 | def test_beep(self): |
|
124 | 130 | """ Are beep characters processed correctly? |
|
125 | 131 | """ |
|
126 |
string = 'foo\ |
|
|
127 | self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar']) | |
|
128 | self.assertEquals(len(self.processor.actions), 1) | |
|
129 |
|
|
|
130 | self.assertEquals(action.action, 'beep') | |
|
132 | string = 'foo\abar' # bell | |
|
133 | splits = [] | |
|
134 | actions = [] | |
|
135 | for split in self.processor.split_string(string): | |
|
136 | splits.append(split) | |
|
137 | actions.append([action.action for action in self.processor.actions]) | |
|
138 | self.assertEquals(splits, ['foo', None, 'bar']) | |
|
139 | self.assertEquals(actions, [[], ['beep'], []]) | |
|
140 | ||
|
141 | def test_backspace(self): | |
|
142 | """ Are backspace characters processed correctly? | |
|
143 | """ | |
|
144 | string = 'foo\bbar' # backspace | |
|
145 | splits = [] | |
|
146 | actions = [] | |
|
147 | for split in self.processor.split_string(string): | |
|
148 | splits.append(split) | |
|
149 | actions.append([action.action for action in self.processor.actions]) | |
|
150 | self.assertEquals(splits, ['foo', None, 'bar']) | |
|
151 | self.assertEquals(actions, [[], ['backspace'], []]) | |
|
152 | ||
|
153 | def test_combined(self): | |
|
154 | """ Are CR and BS characters processed correctly in combination? | |
|
155 | ||
|
156 | BS is treated as a change in print position, rather than a | |
|
157 | backwards character deletion. Therefore a BS at EOL is | |
|
158 | effectively ignored. | |
|
159 | """ | |
|
160 | string = 'abc\rdef\b' # CR and backspace | |
|
161 | splits = [] | |
|
162 | actions = [] | |
|
163 | for split in self.processor.split_string(string): | |
|
164 | splits.append(split) | |
|
165 | actions.append([action.action for action in self.processor.actions]) | |
|
166 | self.assertEquals(splits, ['abc', None, 'def', None]) | |
|
167 | self.assertEquals(actions, [[], ['carriage-return'], [], ['backspace']]) | |
|
131 | 168 | |
|
132 | 169 | |
|
133 | 170 | if __name__ == '__main__': |
General Comments 0
You need to be logged in to leave comments.
Login now