##// END OF EJS Templates
BUG: qtconsole -- non-standard handling of \a and \b. [Fixes #1561]
Puneeth Chaganti -
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 if groups[0] == '\r':
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 groups[0].startswith('['):
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 groups[0].startswith(']'):
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 action = self.processor.actions[0]
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 action = self.processor.actions[0]
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\bbar' # form feed
127 self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar'])
128 self.assertEquals(len(self.processor.actions), 1)
129 action = self.processor.actions[0]
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